JavaSE - 16JDK8新特性2
(5)强大的Stream API
(5.1)StreamAPI说明
- Java8中两大最为重要的改变,一个是Lambda表达式;另一个就是Stream API。
- StreamAPI(java.util.stream)把真正的函数式编程风格引入到Java中,让程序员可以写出高效、干净、简洁的代码。
- StreamAPI是Java8中处理集合的关键抽象概念,可以指定希望对集合进行的操作,也可以执行非常复杂的查找、过滤和映射数据等操作。
- 使用StreamAPI对集合数据进行操作,类似于使用SQL执行的数据库查询。也可以使用StreamAPI来并行执行操作。
- Collection 和 Stream集合的区别: Collection是一种静态的内存数据结构,而Stream是有关计算的。
- 前者是主要面向内存, 存储在内存中,后者主要是面向CPU, 通过CPU实现计算。
(5.2)什么是Stream
- Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。 集合讲的是数据,Stream讲的是计算。
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反, 他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。 这意味着他们会等到需要结果的时候才执行。
- Stream 操作步骤:
-
创建Stream: 一个数据源(如:集合、数组),获取一个流
-
中间操作: 一个中间操作链,对数据源的数据进行处理
-
终止操作: 一旦执行终止操作,就执行中间操作链,并产生结果通知。之后,不会再被使用。
-
(5.3)Stream 创建
(5.3.1)创建Stream方式一: 通过集合
- Java 8中的Collection 接口被扩展,提供了两个获取流的方法:
- default Stream<E> stream(): 返回一个顺序流
- default Stream<E> parallelStream(): 返回一个并行流
List<Employee> employees = EmployeeData.getEmployees(); // default Stream<E> stream(): 返回一个顺序流 Stream<Employee> stream = employees.stream(); // parallelStream(): 返回一个并行流 Stream<Employee> parallelStream = employees.parallelStream();
(5.3.2)创建Stream方式二: 通过数组
// 调用Arrays类的 static <T> Stream<T> stream(T[] array): 返回一个流 int[] arr = new int[]{1,2,3,45,6}; IntStream stream2 = Arrays.stream(arr); Employee e1 = new Employee(1,"Tom"); Employee e2 = new Employee(2,"Jack"); Employee[] emp = new Employee[]{e1,e2}; Stream<Employee> stream3 = Arrays.stream(emp);
(5.3.3)创建Stream方式三: 通过Stream的of()
Stream<Integer> stream4 = Stream.of(1,2,3,4,5,6);
Stream<Employee> stream5 = Stream.of(e1,e2);
(5.3.4)创建Stream方式四: 创建无限流
// 迭代:public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) Stream.iterate(0, t -> t+2).forEach(System.out::println); // 遍历前10个偶数 Stream.iterate(0, t -> t+2).limit(10).forEach(System.out::println); // 生成: public static<T> generate(Supplier<T> s) Stream.generate(Math::random).limit(10).forEach(System.out::println);
(5.4)Stream 中间操作
- 多个中间操作可以连接起来形成一个流水线, 除非流水线上触发终止操作,否则中间操作不会执行任何的处理。
- 而在终止操作时一次性全部处理,称为"惰性求值"。
(5.4.1)筛选与切片
- filter(Predicate p): 接收Lambda, 从流中排除某些元素
- distinct(): 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
- limit(long maxSize): 截断流, 使其元素不超过给定数量
- skip(long n): 跳过元素,返回一个去掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补。
List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> stream = employees.stream(); stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println); stream.limit(3).forEach(System.out::println); stream.skip(3).forEach(System.out::println); stream.distinct().forEach(System.out::println);
(5.4.2)映射
- map(Function f): 接收一个函数作为参数,该参数会被应用到每个元素上,并将其映射成一个新的元素。
- mapToDouble(ToDoubleFunction f): 接收一个函数作为参数,该参数会被应用到每个元素上,产生一个新的DoubleStream。
- mapToInt(ToIntFunction f): 接收一个函数作为参数,该参数会被应用到每个元素上,产生一个新的IntStream。
- mapToLong(ToLongFunction f): 接收一个函数作为参数,该参数会被应用到每个元素上,产生一个新的LongStream。
- flatMap(Function f): 接收一个函数作为参数,将流中的每个值都转换成另一个流,然后把所有的流连接成一个流。
List<String> list = Arrays.asList("aa","vv","cc","dd"); list.stream().map(str -> str.toUpperCase()).forEach(System.out::println); List<Employee> employees = EmployeeData.getEmployees(); Stream<Employee> stream = employees.stream(); Stream<String> nameStream = stream.map(Employee::getName); nameStream.filter(name -> name.length() > 3).forEach(System.out::println); Stream<Stream<Character>> streamStream = list.stream().map(StreamTest::fromStringToStream); streamStream.forEach(s -> { s.forEach(System.out::println); }); Stream<Character> characterStream = list.stream().flatMap(StreamTest::fromStringToStream); characterStream.forEach(System.out::println);
public static Stream<Character> fromStringToStream(String str){ ArrayList<Character> list = new ArrayList<>(); for(Character c: str.toCharArray()){ list.add(c); } return list.stream(); }
(5.4.3)排序
// sorted():自然排序 List<Integer> list = Arrays.asList(12,43,65,34,87,1,-9,19); list.stream().sorted().forEach(System.out::println); // sorted(Comparator com): 定制排序 List<Employee> employees = EmployeeData.getEmployees(); employees.stream().sorted((e1,e2) ->{return Integer.compare(e1.getAge(), e2.getAge());}).forEach(System.out::println); employees.stream().sorted((e1,e2) ->{ int ageValue = Integer.compare(e1.getAge(), e2.getAge()); if(ageValue != 0){ return ageValue; }else{ return Double.compare(e1.getSalary(), e2.getSalary()); } }).forEach(System.out::println);
(5.5)Stream 终止操作
- 终端操作会从流的流水线生成结果。其结果可以是 任何不是流的值,如List、Integer,甚至是 void。
- 流进行了终止操作后,不能再次使用。
(5.5.1)匹配与查找
- allMatch(Predicate p): 检查是否匹配所有元素
- anyMatch(Predicate p): 检查是否至少匹配一个元素
- noneMatch(Predicate p): 检查是否没有匹配所有元素
- findFirst(): 返回第一个元素
- findAny(): 返回当前流中的任意元素
// 是否所有的员工年龄都大于18 boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18); System.out.println(allMatch); // 是否有任意一个员工的工资大于10000 boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000); System.out.println(anyMatch); // 是否所有员工都不姓"雷", boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷")); System.out.println(noneMatch); // 返回第一个元素 Optional<Employee> firstEmp = employees.stream().findFirst(); System.out.println(firstEmp); // 返回任意元素 Optional<Employee> anyEmp = employees.parallelStream().findAny(); System.out.println(anyEmp);
// 返回最大值 Stream<Double> salaryStream = employees.stream().map(Employee::getSalary); Optional<Double> maxSalary = salaryStream.max(Double::compare); System.out.println(maxSalary.get()); //Optional[9865.38] 9865.38 // 返回最小值 Optional<Employee> minSalaryEmp = employees.stream().min((e1,e2) -> Double.compare(e1.getSalary(),e2.getSalary())); System.out.println(minSalaryEmp); //Optional[Employee{id=1008, name='扎克伯格', age=35, salary=2500.38}] // forEach(Consumer c): 内部迭代 employees.stream().forEach(System.out::println);
(5.5.2)归约
- reduce(T iden, BinaryOperator b): 可以将流中元素反复结合起来,得到一个值。返回T
- reduce(BinaryOperator b): 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
- 备注: map 和reduce 的连接通常称为 map-reduce模式,因Google用它进行网络搜索而出名。
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); int sum = list.stream().reduce(0,Integer::sum); System.out.println(sum); // 55 List<Employee> employees = EmployeeData.getEmployees(); Optional<Double> salarySum = employees.stream().map(e -> e.getSalary()).reduce(Double::sum); System.out.println(salarySum); // Optional[48675.03999999999] Optional<Double> salarySum2 = employees.stream().map(Employee::getSalary).reduce((d1,d2) -> d1+d2); System.out.println(salarySum2); // Optional[48675.03999999999]
(5.5.2)收集
- collect(Collector c): 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
另外,Collectors实用类提供了很多静态方法,可以创建常见收集器实例。
方法 | 返回类型 | 作用 |
toList | List<T> | 把流中元素收集到List |
toSet | Set<T> | 把流中元素收集到Set |
toCollection | Collection<T> | 把流中元素收集到创建的集合 |
counting | Long | 计算流中元素的个数 |
summingInt | Integer | 对流中元素的整数属性求和 |
avaragingInt | Double | 计算流中元素Integer属性的平均值 |
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。如平均值、最大值、最小值、总数量 |
List<Employee> employees = EmployeeData.getEmployees(); List<Employee> empList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList()); empList.forEach(System.out::println); Set<Employee> empSet= employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet()); empSet.forEach(System.out::println);
Collection<Employee> emps = employees.stream().collect(Collectors.toCollection(ArrayList::new)); System.out.println(emps); long count = employees.stream().collect(Collectors.counting()); System.out.println(count); // 8 double totalSalary = employees.stream().collect(Collectors.summingDouble(Employee::getSalary)); System.out.println(totalSalary); //48675.04 double avgSalary = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary)); System.out.println(avgSalary); //6084.38 DoubleSummaryStatistics dss = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.println(dss.getMax() + "--" + dss.getMin() + "--" + dss.getAverage()); //9865.38--2500.38--6084.38
(6)Optional类
- 为了解决空指针异常,Google公司的Guava项目引入了Optional类,Guava通过使用检查空值的方法来防止代码污染。
- 受Google Guava启发,Optional类已经成为Java 8类库的一部分。
- Optional<T> 类(java.util.Optional)是一个容器类,可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。
- 原来用null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以避免空指针异常。
- Optional类的JavaDoc描述如下: 这是一个可以为null的容器对象。
- 如果值存在则isPresent()方法会返回true, 调用get()方法会返回该对象。
(6.1)Optional类方法
- 创建Optional类对象的方法:
- Optional.of(T t): 创建一个 Optional实例,t 必须非空
- Optional.empty(): 创建一个空的Optional实例
- Optional.ofNullable(T t): t 可以为null
- 判断Optional 容器中是否包含对象:
- boolean isPresent(): 判断是否包含对象
- void ifPresent(Consumer<? super T> consumer): 如果有值,执行Consumer接口的实现代码,并且该值会作为参数传给它。
- 获取Optional容器的对象
- T get(): 如果调用对象包含值,返回该值,否则抛异常
- T orElse(T other): 如果有值则将其返回,否则返回指定的other对象
- T orElseGet(Supplier<? extends T> other): 如果有值将其返回,否则返回由Supplier接口实现提供的对象。
- T orElseThrow(Supplier<? extends X> exceptionSupplier): 如果有值将其返回,否则抛出由Supplier接口实现提供的异常。
Girl girl = new Girl(); girl = null; // Optional<Girl> optionalGirl = Optional.of(girl); // java.lang.NullPointerException Girl girl2 = new Girl(); girl2 = null; Optional<Girl> optionalGirl2 = Optional.ofNullable(girl2); Girl girl3 = optionalGirl2.orElse(new Girl("春天",11)); System.out.println(girl3); // Girl{name='null', age=0, boy=null} 未添加 girl2 =null时 // Girl{name='春天', age=11, boy=null}
未做判断时: 报空指针异常
public String getGirlName(Boy boy){ return boy.getGirl().getName(); } @Test public void test2(){ Boy boy = new Boy(); String girlName = getGirlName(boy); // java.lang.NullPointerException System.out.println(girlName); }
做判断: 返回null
public String getGirlName(Boy boy){ // return boy.getGirl().getName(); if(boy != null){ Girl girl = boy.getGirl(); if(girl != null){ return girl.getName(); } } return null; }
使用Optional 实现
public String getGirlName2(Boy boy){ Optional<Boy> boyOptional = Optional.ofNullable(boy); Boy boy2 = boyOptional.orElse(new Boy(new Girl("地理",23))); Girl girl = boy2.getGirl(); Optional<Girl> girlOptional = Optional.ofNullable(girl); Girl girl2 = girlOptional.orElse(new Girl("古老",12)); return girl2.getName(); }
// boy=null时 boy2将Girl进行赋值; boy=new Boy()时 boy非空,到了 girl2时对Girl进行赋值 @Test public void test3(){ Boy boy = null; boy = new Boy(); String girlName = getGirlName2(boy); System.out.println(girlName); // boy=null: 地理 boy=new Boy():古老 }
Optional<Object> op1 = Optional.empty(); if(!op1.isPresent()){ System.out.println("数据为空"); // 数据为空 } System.out.println(op1); // Optional.empty System.out.println(op1.isPresent()); // false System.out.println(op1.get()); // java.util.NoSuchElementException: No value present
String str = "hello"; str = null; // of()要求t非空,如t为空,get()方法报错 Optional<String> op2 = Optional.of(str); System.out.println(op2.get()); // hello 加入str=null: java.lang.NullPointerException
String str2 = "beijn"; str2 = null; Optional<String> op3 = Optional.ofNullable(str2); String str3 = op3.orElse("shanho"); System.out.println(str3); // beijn 加入str2=null: shanho