并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。 Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
public class LambdaTest { @Test public void test1(){ Runnable runnable1 = new Runnable() { @Override public void run() { System.out.println("我爱北京天安门"); } }; runnable1.run(); } //Lambda表达式 @Test public void test2(){ Runnable runnable2 = () -> System.out.println("我爱北京故宫"); runnable2.run(); } @Test public void test3(){ Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1,o2); } }; int compare1 = com1.compare(12, 21); System.out.println(compare1); //Lambda表达式的写法 Comparator<Integer> com2 = (o1,o2) -> Integer.compare(o1,o2); int compare2 = com2.compare(32, 21); System.out.println(compare2); //方法引用的写法 Comparator<Integer> com3 = Integer :: compare; int compare3 = com3.compare(32, 21); System.out.println(compare3); } }
Lambda表达式的使用
1.举例:(o1,o2) -> Integer.compare(o1,o2);
2.格式:
-> :Lambda操作符 或 箭头操作符
->左边:Lambda形参列表 (其实就是接口中的抽象方法的形参列表)
->右边:Lambda体 (其实就是重写的抽象方法的方法体)
3.Lambda表达式的使用:(分为6种情况介绍)
总结:
->左边:Lambda形参列表的参数类型可以省略(类型推断);如果Lambda形参列表只有一个参数,其一对()也可以省略。
->右边:Lambda体应该使用一对 {} 包裹;如果Lambda体只有一条执行语句(可能是return语句),省略这一对{} 和return关键字
4.Lambda表达式的本质:作为函数式接口的实例
5.如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
6.所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
7.在java.util.function包下定义了Java 8 的丰富的函数式接口。
6种情况举例:
//语法格式一:无参,无返回值 @Test public void test4(){ Runnable runnable1 = new Runnable() { @Override public void run() { System.out.println("我爱北京天安门"); } }; runnable1.run(); Runnable runnable2 = () -> { System.out.println("我爱北京故宫");//作为接口的实例。万事万物皆对象 }; runnable2.run(); } //语法格式二:Lambda 需要一个参数,但是没有返回值。 @Test public void test5(){ Consumer<String> con = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; con.accept("谎言和誓言的区别是什么呢?"); Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("一个是听的人当真了,一个是说的人当真了"); } //语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断” @Test public void test6(){ Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("一个是听的人当真了,一个是说的人当真了"); //前面泛型对类型已经进行了规范,所以类型就已经确定下来了 Consumer<String> con2 = (s) -> { System.out.println(s); }; con2.accept("一个是听的人当真了,一个是说的人当真了"); //类型推断在之前的应用 ArrayList<String> list = new ArrayList<>();//类型推断 int[] arr = {1,2,3};//类型推断 } //语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略 @Test public void test7(){ Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("一个是听的人当真了,一个是说的人当真了"); //前面泛型对类型已经进行了规范,所以类型就已经确定下来了 Consumer<String> con2 = s -> { System.out.println(s); }; con2.accept("一个是听的人当真了,一个是说的人当真了"); } //语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值 @Test public void test8(){ Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); } }; System.out.println(com1.compare(12,21)); Comparator<Integer> com2 = (o1,o2) -> { System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); }; System.out.println(com2.compare(32,21)); } //语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略 @Test public void test9(){ Comparator<Integer> com2 = (o1,o2) -> { return o1.compareTo(o2); }; System.out.println(com2.compare(32,21)); Comparator<Integer> com3 = (o1,o2) -> o1.compareTo(o2); System.out.println(com3.compare(32,21)); Consumer<String> con1 = (String s) -> { System.out.println(s); }; con1.accept("一个是听的人当真了,一个是说的人当真了"); //只有一条执行语句的时候,大括号才可以省略 Consumer<String> con2 = (String s) -> System.out.println(s); con2.accept("一个是听的人当真了,一个是说的人当真了"); }
自定义函数式接口:
//自定义函数式接口 @FunctionalInterface public interface MyInterface { void method1(); }
以前用匿名实现类表示的现在都可以用Lambda表达式来写,当然还用之前的匿名实现类也是可以的。
Java内置的4大核心函数式接口
类型 | 接口 | 方法 |
---|---|---|
消费型接口 | Consumer | void accept(T t) |
供给型接口 | Supplier | T get() |
函数型接口 | Function<T,R> | R apply(T t) |
断定型接口 | Predicate | boolean test(T t) |
举例:
//使用函数式接口举例一 @Test public void test10(){ happyTime(500, new Consumer<Double>() { @Override public void accept(Double aDouble) { System.out.println("吃一顿饭花费为" + aDouble); } }); //使用Lambda表达式 happyTime(400,aDouble -> System.out.println("吃一顿饭花费为" + aDouble)); } private void happyTime(double money,Consumer<Double> con){ con.accept(money); } //使用函数式接口举例二 @Test public void test11(){ List<String> list = Arrays.asList("北京","南京","东京","天津"); List<String> filterStrs = filetString(list, new Predicate<String>() { @Override public boolean test(String s) { return s.contains("京"); } }); System.out.println(filterStrs); //Lambda表达式 List<String> filterStrs1 = filetString(list,s -> s.contains("京")); System.out.println(filterStrs1); } //根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定。 private List<String> filetString(List<String> list, Predicate<String> pre){ ArrayList<String> filterList = new ArrayList<>(); for (String s: list) { if (pre.test(s)){ filterList.add(s); } } return filterList; }
出现以上接口,都可以用Lambda表达式去写,可以不用匿名实现类了。
何时使用Lambda表达式?
当需要对一个函数式接口实例化的时候,可以使用lambda表达式。
何时使用给定的函数式接口?
如果我们开发中需要定义一个函数式接口,首先看看在已有的jdk提供的函数式接口是否提供了能满足需求的函数式接口。如果有,则直接调用即可,不需要自己再自定义了。
方法引用的使用
1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以方法引用,也是函数式接口的实例。
3.使用格式: 类(或对象) :: 方法名 【注意这里只有方法名,不需要参数列表】
4.具体分为如下的三种情况:
情况一 对象 :: 非静态方法
情况二 类 :: 静态方法
情况三 类 :: 非静态方法 (注意这里跟面向对象那部分有些区别,这里类是可以调非静态方法的)
5.方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同!(针对于情况一和情况二)。而情况三也有一定的规律,即当第一个参数作为调用者时可以考虑方法引用。
/** * 方法引用的使用 * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用! * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以方法引用,也是函数式接口的 * 实例。 * 3.使用格式: 类(或对象) :: 方法名 【注意这里只有方法名,不需要参数列表】 * 4.具体分为如下的三种情况: * 情况一 对象 :: 非静态方法 * 情况二 类 :: 静态方法 * * 情况三 类 :: 非静态方法 (注意这里跟面向对象那部分有些区别,这里类是可以调非静态方法的) * 5.方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的 * 方法的形参列表和返回值类型相同!(针对于情况一和情况二) */ public class MethodRefTest { // 情况一:对象 :: 实例方法 //Consumer中的void accept(T t) //PrintStream中的void println(T t) @Test public void test1() { //Lambda表达式 Consumer<String> con1 = s -> System.out.println(s); con1.accept("北京"); //方法引用 对象 :: 非静态方法 PrintStream ps = System.out; Consumer<String> con2 = ps :: println; con2.accept("beijing"); } //Supplier中的T get() //Employee中的String getName() @Test public void test2() { //Lambda表达式 Employee employee = new Employee(1001,"Tom",23,5600); Supplier<String> sup1 = () -> employee.getName(); System.out.println(sup1.get()); //方法引用 对象 :: 非静态方法 Supplier<String> sup2 = employee::getName; System.out.println(sup2.get()); } // 情况二:类 :: 静态方法 //Comparator中的int compare(T t1,T t2) //Integer中的int compare(T t1,T t2) @Test public void test3() { //Lambda表达式 Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2); System.out.println(com1.compare(12,21)); //方法引用 类 :: 静态方法 Comparator<Integer> com2 = Integer :: compare; System.out.println(com2.compare(12,21)); } //Function中的R apply(T t) //Math中的Long round(Double d) @Test public void test4() { //匿名类 Function<Double,Long> fun1 = new Function<Double, Long>() { @Override public Long apply(Double d) { return Math.round(d); } }; System.out.println(fun1.apply(5.4)); //Lambda表达式 Function<Double,Long> fun2 = d -> Math.round(d); System.out.println(fun2.apply(6.5)); 方法引用 类 :: 静态方法 Function<Double,Long> fun3 = Math::round; System.out.println(fun3.apply(8.5)); } // 情况三:类 :: 实例方法 //情况三虽然形参列表不对应了,但是也有一定的规律。第一个参数作为调用者时可以考虑方法引用 // Comparator中的int comapre(T t1,T t2) // String中的int t1.compareTo(t2) @Test public void test5() { //Lambda表达式 Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2); System.out.println(com1.compare("abc","abd")); //方法引用 类 :: 实例方法 Comparator<String> com2 = String::compareTo; System.out.println(com2.compare("abc","abm")); } //BiPredicate中的boolean test(T t1, T t2); //String中的boolean t1.equals(t2) @Test public void test6() { //Lambda表达式 BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2); System.out.println(pre1.test("abc","abc")); //方法引用 类 :: 实例方法 BiPredicate<String,String> pre2 = String::equals; System.out.println(pre2.test("abc","abd")); } // Function中的R apply(T t) // Employee中的String getName(); @Test public void test7() { Employee employee = new Employee(1001,"Jerry",23,6000); //Lambda表达式 Function<Employee,String> fun1 = e -> e.getName(); System.out.println(fun1.apply(employee)); //方法引用 类 :: 实例方法 Function<Employee,String> fun2 = Employee::getName; System.out.println(fun2.apply(employee)); } }
总结:当可以使用方法引用的时候可以使用方法引用,再者考虑Lambda表达式,如果这两个都不会则只能用匿名类。
使用建议:如果给函数式接口提供实例,恰好满足方法引用的使用情境,就可以考虑使用方法引用给函数式接口提供实例。如果不熟悉方法引用,那么还可以使用Lambda表达式。
一、构造器引用
格式: 类名 :: new
和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型。
二、数组引用
格式: 数组类型[] :: new
大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
/** * 一、构造器引用 * 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。 * 抽象方法的返回值类型即为构造器所属的类的类型。 * 二、数组引用 * 大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。 */ public class ConstructorRefTest { //构造器引用 //Supplier中的T get() //Employee的空参构造器:Employee() @Test public void test1(){ //匿名类 Supplier<Employee> sup1 = new Supplier<Employee>() { @Override public Employee get() { return new Employee(); } }; //Lambda表达式 Supplier<Employee> sup2 = () -> new Employee(); //构造器引用 Supplier<Employee> sup3 = Employee::new; } //Function中的R apply(T t) //Employee的构造器:Employee(id) @Test public void test2(){ Function<Integer,Employee> func1 = new Function<Integer, Employee>() { @Override public Employee apply(Integer integer) { return new Employee(integer); } }; //Lambda表达式 Function<Integer,Employee> func2 = id -> new Employee(id); func2.apply(1001); //构造器引用 Function<Integer,Employee> func3 = Employee::new; func3.apply(1002); } //BiFunction中的R apply(T t,U u) //Employee的构造器:Employee(id,name) @Test public void test3(){ BiFunction<Integer,String,Employee> func1 = new BiFunction<Integer, String, Employee>() { @Override public Employee apply(Integer integer, String s) { return new Employee(integer,s); } }; //Lambda表达式 BiFunction<Integer,String,Employee> func2 = (id,name) -> new Employee(id,name); func2.apply(1001,"Jerry"); //构造器引用 BiFunction<Integer,String,Employee> func3 = Employee::new; func3.apply(1002,"Tom"); } //数组引用 //Function中的R apply(T t) @Test public void test4(){ Function<Integer,String[]> func1 = new Function<Integer, String[]>() { @Override public String[] apply(Integer integer) { return new String[integer]; } }; //Lambda表达式 Function<Integer,String[]> func2 = length -> new String[length]; String[] arr1 = func2.apply(10); System.out.println(Arrays.toString(arr1)); //数组引用 Function<Integer,String[]> func3 = String[]::new; String[] arr2 = func3.apply(5); System.out.println(Arrays.toString(arr2)); } }
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream 和 Collection 集合的区别:
Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
集合讲的是数据,Stream讲的是计算!
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream 的操作三个步骤:
1- 创建 Stream
一个数据源(如:集合、数组),获取一个流
2- 中间操作
一个中间操作链,对数据源的数据进行处理
3- 终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用。
所谓延迟执行,就是只有在执行终止操作后,一系列的中间操作才会开始执行,且执行完毕就会终止,不可再被使用。
1.Stream关注的是对数据的运算,与CPU打交道
集合关注的是数据的存储,与内存打交道
就是java8提供了一套API,使用这套API可以对内存中的数据进行过滤、排序、映射、归约等操作。类似于sql对数据库中表的相关操作。
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
3.Stream执行流程
①Stream的实例化
②一系列中间操作(过滤、映射、…)
③终止操作
4.说明
①一个中间操作链,对数据源的数据进行处理
②一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
四种方式,主要看前三种。需要集合就用第一种,需要数组就第二种,需要直接创建一个流就用第三种,需要生成一些数据就用第四种。
//测试Stream的实例化 public class StreamAPITest { @Test public void test1(){ //创建 Stream方式一:通过集合 List<Employee> employees = EmployeeData.getEmployees(); //default Stream<E> stream() : 返回一个顺序流 Stream<Employee> stream = employees.stream(); //default Stream<E> parallelStream() : 返回一个并行流 Stream<Employee> employeeStream = employees.parallelStream(); } @Test public void test2(){ //创建 Stream方式二:通过数组 //调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流 int[] arr1 = new int[]{1,2,3,4,5}; IntStream stream = Arrays.stream(arr1); Employee e1 = new Employee(1001,"Jerry"); Employee e2 = new Employee(1002,"Tom"); Employee[] arr2 = new Employee[]{e1,e2}; Stream<Employee> stream1 = Arrays.stream(arr2); } @Test public void test3(){ //创建 Stream方式三:通过Stream的of() //public static<T> Stream<T> of(T... values) : 返回一个流 //可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。 Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6); } @Test public void test4(){ //创建 Stream方式四:创建无限流.作用就是用来造数据 //可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。 //迭代 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) Stream.iterate(0,t -> t + 2).limit(10).forEach(System.out::println);//遍历前10个偶数 //生成 public static<T> Stream<T> generate(Supplier<T> s) Stream.generate(Math::random).limit(10).forEach(System.out::println); } }
在情况四中," .forEach() "相当于消费者,用来执行终止操作,只有执行了终止操作,中间操作才会开始执行。
filter(Predicate p)——接收Lambda,从流中排除某些元素。
limit(n)——截断流,使其元素不超过给定数量。
skip(n)——跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流。与limit(n)互补
distinct()——筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素
/** * 测试Stream的中间操作 */ @Test public void test5(){ //1.筛选与切片 //filter(Predicate p)——接收Lambda,从流中排除某些元素。 List<Employee> list = EmployeeData.getEmployees(); Stream<Employee> stream = list.stream(); //练习查询员工表中薪资大于7000的员工信息 stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);//forEach相当于终止操作 //limit(n)——截断流,使其元素不超过给定数量。 //一定要重新生成一个流,因为执行完终止操作后就结束了,不能再去执行中间操作了,所以需要重新生成一个流 //类似于迭代器Iterator,每次使用都要重新生成 list.stream().limit(3).forEach(System.out::println); //skip(n)——跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流。与limit(n)互补 list.stream().skip(3).forEach(System.out::println); //distinct()——筛选,通过流所生成元素的hashCode() 和 equals() 去除重复元素 list.stream().distinct().forEach(System.out::println); }
映射的第二个比较难理解,需要及时复习。
@Test public void test6(){ //2.映射 //map(Function f)——接收一个函数作为参数,将元素转换为其他形式或提取信息,该函数会被应用到 //每个元素上,并将其映射成一个新的元素。 List<String> list = Arrays.asList("aa", "bb", "cc"); list.stream().map(str -> str.toUpperCase()).forEach(System.out::println); //练习1:获取员工姓名长度大于3的员工的姓名 List<Employee> employees = EmployeeData.getEmployees(); // Stream<String> namesStream = employees.stream().map(e -> e.getName());//Lambda表达式 Stream<String> namesStream = employees.stream().map(Employee::getName);//方法引用 namesStream.filter(name -> name.length() > 3).forEach(System.out::println); //练习2:遍历每一个元素 Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest::fromStringToStream); streamStream.forEach(s -> s.forEach(System.out::println)); //flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。 Stream<Character> characterStream = list.stream().flatMap(StreamAPITest::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(); } @Test public void test7(){ ArrayList list1 = new ArrayList(); list1.add(1); list1.add(2); list1.add(3); ArrayList list2 = new ArrayList(); list2.add(4); list2.add(5); list2.add(6); list1.add(list2);//相当于map(Function f) list1.addAll(list2);//相当于flatMap(Function f) System.out.println(list1); }
在Java层面涉及到排序就去想Comparable和Comparator。
@Test public void test8(){ //3.排序 在Java层面涉及到排序就去想Comparable和Comparator //sorted()——自然排序 List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7); list.stream().sorted().forEach(System.out::println); //抛异常,原因:Employee没有实现Comparable接口 // List<Employee> employees = EmployeeData.getEmployees(); // employees.stream().sorted().forEach(System.out::println); //sorted(Comparator com)——定制排序 List<Employee> employees = EmployeeData.getEmployees(); employees.stream().sorted(new Comparator<Employee>() { @Override public int compare(Employee o1, Employee o2) { int ageValue = Integer.compare(o1.getAge(),o2.getAge()); if (ageValue != 0){ return ageValue; }else { return Double.compare(o1.getSalary(),o2.getSalary()); } } }).forEach(System.out::println); //Lambda表达式实现 employees.stream().sorted((o1,o2) -> { int ageValue = Integer.compare(o1.getAge(),o2.getAge()); if (ageValue != 0){ return ageValue; }else { return Double.compare(o1.getSalary(),o2.getSalary()); } }).forEach(System.out::println); }
/** * Stream终止操作 * 1.匹配与查找 */ @Test public void test9(){ List<Employee> employees = EmployeeData.getEmployees(); //allMatch(Predicate p) 检查是否匹配所有元素 //练习:是否所有的员工的年龄都大于18 boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18); System.out.println(allMatch); //anyMatch(Predicate p) 检查是否至少匹配一个元素 //练习:是否存在员工的工资大于10000 boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000); System.out.println(anyMatch); //noneMatch(Predicate p) 检查是否没有匹配的元素 //练习:是否存在员工姓“雷” boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷")); System.out.println(noneMatch);//注意该方法检查的 是否没有匹配的元素。 //findFirst() 返回第一个元素 Optional<Employee> employee = employees.stream().findFirst(); System.out.println(employee); //findAny() 返回当前流中的任意元素 Optional<Employee> any = employees.stream().findAny(); System.out.println(any); //count() 返回流中元素总数 long count = employees.stream().filter(e -> e.getSalary() > 5000).count(); System.out.println(count); //max(Comparator c) 返回流中最大值 //练习,返回最高的工资 Stream<Double> doubleStream = employees.stream().map(e -> e.getSalary());//全部映射为薪水流 doubleStream.max(new Comparator<Double>() { @Override public int compare(Double o1, Double o2) { return Double.compare(o1,o2); } });//匿名类 doubleStream.max(((o1, o2) -> Double.compare(o1,o2)));//Lambda表达式 Optional<Double> max = doubleStream.max(Double::compare);//方法引用 System.out.println(max); //min(Comparator c) 返回流中最小值 //练习:返回工资最低的员工 Optional<Employee> min = employees.stream().min(((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()))); System.out.println(min); //forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭 //代——它帮你把迭代做了) employees.stream().forEach(System.out::println);//这个是stream中的 //Collection接口中的Iterator迭代器就是外部迭代 employees.forEach(System.out::println);//注意这是使用的是集合的遍历操作,与上面的不同。 }
归约就是一串数据,先求出前两个数的和,再将这个和与第三个数求和,如此重复操纵便可求出总和。(归约,将两个归成一个)
@Test public void test10(){ //归约 :求和即是归约 //reduce(T identity, BinaryOperator b)——可以将流中元素反复结合起来,得到一个值。返回 T //练习1:计算1-10的自然数的和 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Integer sum = list.stream().reduce(0, Integer::sum);//第一个参数是起始值 System.out.println(sum); //reduce(BinaryOperator b)——可以将流中元素反复结合起来,得到一个值。返回 Optional<T> //练习2:计算公司所有员工工资的总和 List<Employee> employees = EmployeeData.getEmployees(); // Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary()); Stream<Double> salaryStream = employees.stream().map(Employee::getSalary); // Optional<Double> sumMoney = salaryStream.reduce(Double::sum); Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2); System.out.println(sumMoney); }
Collector需要使用Collectors提供实例。
@Test public void test11(){ //收集 //collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 //练习1:查找工资大于6000的员工,结果返回一个List 或 Set List<Employee> employees = EmployeeData.getEmployees(); List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList()); employeeList.forEach(System.out::println); }
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。
另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
Optional类:为了在程序中避免出现空指针异常而创建的。
常用的方法:ofNullable(T t)
orElse(T t)
创建Optional类对象的方法:
判断Optional容器中是否包含对象:
获取Optional容器的对象:
/** * Optional.of(T t) : 创建一个 Optional 实例,t必须非空; * Optional.empty() : 创建一个空的 Optional 实例 * Optional.ofNullable(T t):t可以为null */ public class OptionalTest { @Test public void test1(){ Girl girl = new Girl(); // girl = null; //of(T t):保证t是非空的 Optional<Girl> optionalGirl = Optional.of(girl); System.out.println(optionalGirl); } @Test public void test2(){ Girl girl = new Girl(); girl = null; //ofNullable(T t):t可以为null Optional<Girl> optionalGirl = Optional.ofNullable(girl); System.out.println(optionalGirl); /* orElse(T t1):如果当前的Optional内部封装的t是非空的,则返回内部的t,如果内部的t是空的,则返回orElse()方法中的参数t1. */ Girl girl1 = optionalGirl.orElse(new Girl("Jerry")); System.out.println(girl1); } public String getGirlName(Boy boy){ return boy.getGirl().getName(); } @Test public void test3(){ Boy boy = new Boy(); String girlName = getGirlName(boy); System.out.println(girlName); } //优化以后的getGirlName(): public String getGirlName1(Boy boy){ if (boy != null){ Girl girl = boy.getGirl(); if (girl != null){ return girl.getName(); } } return null; } //使用Optional类的getGirlName(): public String getGirlName2(Boy boy){ Optional<Boy> boyOptional = Optional.ofNullable(boy); //此时的boy1一定非空 Boy boy1 = boyOptional.orElse(new Boy(new Girl("Candy"))); Girl girl = boy1.getGirl(); Optional<Girl> optionalGirl = Optional.ofNullable(girl); //girl1一定非空 Girl girl1 = optionalGirl.orElse(new Girl("Andy")); return girl1.getName(); } }
empty():创建的Optional对象内部的value = null 。
为保证非空,ofNullable(T t) 与orElse (T other) 搭配使用。
of(T t): 封装数据t生成Optional对象。要求t非空,否则报错。
如果Optional封装的数据value为空,则get()报错。否则,value不为空时,返回value值。又of(T t) 要求t非空,因此get()通常与of(T t)方法搭配使用。用于获取内部的封装的数据value。
ofNullable(T t):封装数据t赋给Optional内部的value。不要求t非空。
orElse(T t1):如果Optional内部的value非空,则返回此value值。如果value为空,则返回t1。