Java教程

JavaEE基础第16章Java8新特性

本文主要是介绍JavaEE基础第16章Java8新特性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

第16章Java8新特性

新特性简介

  • 速度更快
  • 代码更少(增加了新的语法:Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn引擎,允许在JVM上运行JS应用

并行流与串行流:

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。 Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

Lambda表达式

举例:

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大核心函数式接口

类型接口方法
消费型接口Consumervoid accept(T t)
供给型接口SupplierT get()
函数型接口Function<T,R>R apply(T t)
断定型接口Predicateboolean 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 API

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的实例化

四种方式,主要看前三种。需要集合就用第一种,需要数组就第二种,需要直接创建一个流就用第三种,需要生成一些数据就用第四种。

//测试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() "相当于消费者,用来执行终止操作,只有执行了终止操作,中间操作才会开始执行。

Stream中间操作

筛选与切片

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终止操作

查找与匹配

/**
 * 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类

Optional类:为了在程序中避免出现空指针异常而创建的

常用的方法:ofNullable(T t)

​ orElse(T t)

创建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接口实现提供的异常。
/**
 * 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。

这篇关于JavaEE基础第16章Java8新特性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!