Java8 中Stream API介绍
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
流(Stream)的概念:流是数据渠道,用于操作数据(集合、数组等)所生成的元素序列。
注意:
- Stream自己不会存储元素。
- Stream不会改便源对象,相反,它们会返回一个持有结果的新Stream。
- Stream操作是延迟执行的,这意味着他们会等到需要结果的时候执行。
Stream的操作三个步骤:
- 创建Stream:一个数据源(如数组、集合),获取一个流
- 中间操作:一个中间操作链,对数据源的数据进行处理
- 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果
创建Stream
创建流的方式有如下几种方式:
//创建Stream @Test public void test01(){ //1.可以通过Collection系列集合提供的stream() 或 parallelStream() List<String> list = new ArrayList(); Stream<String> stream01 = list.stream(); //2、通过Arrays中的静态方法stream() 获取数组流 Emp[] emps = new Emp[10]; Stream<Emp> stream02 = Arrays.stream(emps); //3.通过Stream类中的静态方法of() Stream<String> stream03 = Stream.of("aa","bb","cc"); //4.创建无限流 //迭代 Stream<Integer> stream04 = Stream.iterate(0,(x) -> x+2); stream04.forEach(System.out::println); //只要10个数据 stream04.limit(10).forEach(System.out::println); //生成5个随机数 Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println); }
Stream中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。
筛选与切片:
- filter -------- 接受Lambda ,从流中排除某些元素
- limit --------- 截断流,使其元素不超过给定数量
- skip(n) ------- 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
- distinct ------ 筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素
映射:
- map ------ 接收Lambda,将元素转换为其他形式或提取信息(接受一个函数作为参数,该函数被应用到每个元素上,并将其映射成一个新的元素)
- flatMap ---- 接收一个函数作为参数,将流中的每个值都换成另外一个流,然后把所有的流连凑成一个流。
排序:
- sorted() ---- 自然排序
- sorted(Comparator comparator) ------ 定制排序(Comparator )
下面通过代码来练习这些中间操作,先创建一个Employee实体类:
public class Employee { private String name; private Integer age; private Double salary; public Employee(String name, Integer age, Double salary) { this.name = name; this.age = age; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return Objects.equals(name, employee.name) && Objects.equals(age, employee.age) && Objects.equals(salary, employee.salary); } @Override public int hashCode() { return Objects.hash(name, age, salary); } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}'; } }
测试中间操作filter的用法:
List<Employee> emps = Arrays.asList( new Employee("张三",21,4500.00), new Employee("李四",25,6000.00), new Employee("王五",56,3500.00), new Employee("王五",56,3500.00), new Employee("田七",30,8000.00), new Employee("田七",30,8000.00) ); @Test public void test02(){ //中间操作:不会执行任何操作 Stream<Employee> stream = employeeList.stream().filter(e -> e.getAge()>25); //终止操作:一次型执行全部内容,即”惰性求值“ //内部迭代:迭代操作由Stream API 完成 stream.forEach(System.out::println); } //外部迭代 @Test public void test03(){ Iterator<Employee> it = emps.iterator(); while(it.hasNext()){ System.out.println(it.next()); } }
中间操作:limit --只要找到符合条件的指定条数数据,就不会执行后面的数据过滤操作了,可以提高效率
@Test public void test04(){ emps.stream().filter(e -> e.getSalary()>3000.00) .limit(2) //短路 : 只要找到符合条件的两条数据,就不会执行后面的数据过滤操作了,可以提高效率 .forEach(System.out::println); }
中间操作:skip(n) ----跳过n个元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
@Test public void test05(){ emps.stream().filter(e -> e.getSalary()>3000.00) .skip(2) .forEach(System.out::println); }
中间操作: distinct ---- 筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素
@Test public void test06(){ emps.stream().filter(e -> e.getSalary()>3000.00) .distinct () .forEach(System.out::println); }
中间操作: map ------ 接受一个函数作为参数,该函数或被应用到每个元素上,并将其映射成一个新的元素
@Test public void test07(){ List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello"); list.stream() //toUpperCase()函数被应用到流中每个元素上,并将其映射成一个新的元素 .map((str) -> str.toUpperCase()) .forEach(System.out::println); //输出结果:AAA BBB CCC DDD HELLO System.out.println("------------------------------"); emps.stream() .map(Employee::getName) .forEach(System.out::println);//输出结果:张三 李四 王五 王五 田七 田七 System.out.println("------------------------------"); Stream<Stream<Character>> stream01 = list.stream() //调用filterCharacter(),将流中的字符串元素都转为字符流,返回值类型为Stream<Stream<Character>> .map(StreamApiTest::filterCharacter); stream01.forEach((sm) -> { sm.forEach(System.out::println); }); System.out.println("------------------------------"); } //字符串转为字符流 public static Stream<Character> filterCharacter(String str){ List<Character> list = new ArrayList<>(); for(Character ch : str.toCharArray()){ list.add(ch); } return list.stream(); }
中间操作: flatMap --- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有的流连凑成一个流
@Test public void test08(){ List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","hello"); Stream<Character> stream02 = list.stream() //调用filterCharacter(),将流中的字符串元素都转为字符流,并将这些流加入到一个新流中,返回值类型为Stream<Character> .flatMap(StreamApiTest::filterCharacter); stream02.forEach(System.out::println); }
中间操作: sorted() ---- 自然排序
@Test public void test09(){ List<String> list = Arrays.asList("ccc","aaa","ddd","bbb","eee"); list.stream().sorted().forEach(System.out::println); }
中间操作: sorted(Comparator comparator) ------ 定制排序(Comparator )
自定义排序规则,这个根据员工年龄排序,若员工年龄相同,则根据员工姓名排序 --- 升序
@Test public void test10(){ emps.stream() .sorted((e1,e2) -> { if(e1.getAge().equals(e2.getAge())){ return e1.getName().compareTo(e2.getName()); }else{ return e1.getAge().compareTo(e2.getAge()); } }) .forEach(System.out::println); }
终止操作
查找与匹配:
- allMatch ----- 检查是否匹配所有元素
- anyMatch ------ 检查是否至少匹配一个元素
- noneMatch --------- 检查是否没有匹配所有元素
- findFirst ------- 返回第一个元素
- findAny -------- 返回流中的任意元素
- count ----------- 返回流中元素的总个数
- max ------- 返回流中的最大值
- min ------ 返回流中的最小值
归约:可以将流中元素反复结合起来,得到一个值
- reduce(T indentity,BinaryOperator bin) ---- indentity 为起始值
- reduce(BinaryOperator bin)
收集:
- collect ----- 将流装换为其它形式,接受一个Collector接口的实现,用于给Stream中元素汇总的方法
终止操作练习:在此之前,我们先创建一个员工实体类,方便测试效果
public class Employee { private String name; private Integer age; private Double salary; private Status status; public Employee(String name, Integer age, Double salary) { this.name = name; this.age = age; this.salary = salary; } public Employee(String name, Integer age, Double salary, Status status) { this.name = name; this.age = age; this.salary = salary; this.status = status; } public Status getStatus() { return status; } public void setStatus(Status status) { this.status = status; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + ", salary=" + salary + ", status=" + status + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Employee)) return false; Employee employee = (Employee) o; return Objects.equals(getName(), employee.getName()) && Objects.equals(getAge(), employee.getAge()) && Objects.equals(getSalary(), employee.getSalary()) && getStatus() == employee.getStatus(); } @Override public int hashCode() { return Objects.hash(getName(), getAge(), getSalary(), getStatus()); } public enum Status{ BUSY,FREE,VACATION; } }
接下来我们先对 查找与匹配 中的几个终止操作进行代码测试:
List<Employee> employees = Arrays.asList( new Employee("张三",21,4500.00, Employee.Status.BUSY), new Employee("李四",25,6000.00,Employee.Status.VACTION), new Employee("王五",56,3500.00,Employee.Status.BUSY), new Employee("王五",56,3500.00,Employee.Status.BUSY), new Employee("田七",30,8000.00,Employee.Status.FREE), new Employee("田七",30,8000.00,Employee.Status.FREE) ); @Test public void test11(){ //allMatch ----- 检查是否匹配所有元素 boolean b1 = employees.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.FREE)); System.out.println(b1); //输出结果: false //anyMatch ------ 检查是否至少匹配一个元素 boolean b2 = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(b2); //输出结果: true //noneMatch --------- 检查是否没有匹配所有元素 boolean b3 = employees.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY)); System.out.println(b3); //输出结果: false //findFirst ------- 返回第一个元素 //Optional --- 是Java8 提供的处理空指针异常的类 Optional<Employee> employee = employees.stream() //按员工的薪资排序 .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())) //获取第一个员工信息,即薪资最新的员工信息 .findFirst(); System.out.println(employee.get()); //findAny -------- 返回流中的任意元素 //parallelStream --- 获取并行流 Optional<Employee> any = employees.parallelStream() //获取状态为 FREE 的任意一个员工信息 .filter(e -> e.getStatus().equals(Employee.Status.FREE)) .findAny(); System.out.println(any.get()); }
测试终止操作中,count,max,min的运用
@Test public void test12(){ //返回流中元素的总个数 long count = employees.stream().count(); System.out.println(count); //输出结果为:6 Optional<Employee> max = employees.stream(). //获取年龄最大的员工信息 max((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())); System.out.println(max.get()); // 输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY} Optional<Double> min = employees.stream() .map((e) -> e.getSalary()) //获取最低的薪资 .min(Double::compare); System.out.println(min.get());//输出结果为:3500.0 }
终止操作:归约 ---- reduce(T indentity,BinaryOperator) / reduce(BinaryOperator),可以将流中元素反复结合起来,得到一个值
@Test public void test13(){ List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer reduce = list.stream() //0为初始值,将流中的元素按照 lambda体中的方式进行汇总,这里即是通过求和的方式汇总 .reduce(0, (x, y) -> x + y); System.out.println(reduce); // 输出结果为:55 Optional<Double> sum = employees.stream() //获取员工的薪资信息 .map((e) -> e.getSalary()) //调用Double的sum(),对员工薪资进行求和 .reduce(Double::sum); System.out.println(sum.get()); //输出结果为:33500.0 }
终止操作:收集:collect ------- 将流装换为其它形式,接收一个Collector 接口的实现,用于给Stream中元素汇总的方法
@Test public void test14(){ //Collectors工具类对Collector接口提供了很多实现 List<String> list = employees.stream() .map(e -> e.getName()) .collect(Collectors.toList()); System.out.println(list);//输出结果为:[张三, 李四, 王五, 王五, 田七, 田七] Set<Integer> set = employees.stream() .map(e -> e.getAge()) .collect(Collectors.toSet()); System.out.println(set);//输出结果为:[21, 56, 25, 30] HashSet<String> hashSet = employees.stream() .map(e -> e.getName()) .collect(Collectors.toCollection(HashSet::new)); System.out.println(hashSet);//输出结果为:[李四, 张三, 王五, 田七] }
因为collect收集使用是很常见的,接下来我们通过使用collect进行统计、求平均值、总和、最大值、最小值,更加熟悉collect的使用,并了解工具类Collectors中常用的方法
@Test public void test15(){ //总数 Long count = employees.stream() .collect(Collectors.counting()); System.out.println(count); // 输出结果为:6 //获取员工薪资的平均值 Double avgSalary = employees.stream() .collect(Collectors.averagingDouble(Employee::getSalary)); System.out.println(avgSalary);// 输出结果为:5583.333333333333 //获取员工薪资的总和 Double total = employees.stream() .collect(Collectors.summingDouble(Employee::getSalary)); System.out.println(total); // 输出结果为:33500.0 //获取最高薪资的员工信息 Optional<Employee> maxSalary = employees.stream() .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))); System.out.println(maxSalary.get()); //输出结果为:Employee{name='田七', age=30, salary=8000.0, status=FREE} //获取最低薪资的员工信息 Optional<Employee> minSalary = employees.stream() .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))); System.out.println(minSalary.get()); //输出结果为:Employee{name='王五', age=56, salary=3500.0, status=BUSY} }
通过使用collect,对流中元素进行分组、多级分组、分区操作。
@Test public void test16(){ //通过员工状态进行分组 Map<Employee.Status, List<Employee>> statusListMap = employees.stream() .collect(Collectors.groupingBy(Employee::getStatus)); System.out.println(statusListMap); } /** * 多级分组 */ @Test public void test17(){ //通过员工状态进行分组 Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream() .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> { if (e.getAge() < 30) { return "青年"; } else if (e.getAge() < 50) { return "中年"; } else { return "老年"; } }))); System.out.println(map); } /** * 分区 */ @Test public void test18(){ Map<Boolean, List<Employee>> map = employees.stream() .collect(Collectors.partitioningBy(e -> e.getSalary() > 6000)); System.out.println(map); } /** * 将流中的元素,按照指定格式连接 */ @Test public void test19(){ String str = employees.stream() .map(e -> e.getName()) .collect(Collectors.joining(",")); System.out.println(str); //输出结果为: 张三,李四,王五,王五,田七,田七 }
并行流与顺序流
并行流:并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java8中将并行流进行了优化,我们可以很容易地对数据进行并行操作。Stream API可以声明性地通过Parallel()与sequential()在并行流与顺序流之间进行切换。
以下实例我们使用 parallelStream 来输出空字符串的数量:
@Test public void test20(){ List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); // 获取空字符串的数量 long count = strings.parallelStream().filter(string -> string.isEmpty()).count(); System.out.println(count); //输出结果为:2 }
Java8中的Stream API可以极大提高Java我们的的生产力,让我们写出高效率、干净、简洁的代码。
例如:使用Java8来求两个集合的交集、差集、并集
@Test public void test(){ //准备两个集合 List<String> list1 = new ArrayList<String>(); list1.add("aa"); list1.add("bb"); list1.add("cc"); list1.add("dd"); list1.add("ee"); List<String> list2 = new ArrayList<String>(); list2.add("bb"); list2.add("cc"); list2.add("ff"); list2.add("gg"); // 交集 List<String> intersection = list1.stream().filter(item -> list2.contains(item)).collect(toList()); System.out.println("---交集 intersection---"); intersection.parallelStream().forEach(System.out :: println); // 差集 List<String> reduce = list2.stream().filter(item -> !list1.contains(item)).collect(toList()); System.out.println("---差集 reduce2 (list2 - list1)---"); reduce.parallelStream().forEach(System.out :: println); //并集 list1.addAll(list2); List<String> collect = list1.stream().distinct().collect(toList()); System.out.println("并集----去重"); collect.stream().forEach(System.out::println); }
来源:https://www.cnblogs.com/reminis/p/12547137.html