十八、 Java8的其他新特性

Java 8 的好处:

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

18.1 Lambda表达式

Lambda 表达式是 Java 8 中最大的语法变化,以至于 18.2 和 18.3 两节的内容都是基于它发展而来的。

18.1.1 为什么要使用Lambda表达式

Lambda是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了
提升。

18.1.2 Lambda表达式的例子

Lambda 表达式要看懂,因为后面的框架底层代码中会使用到 Lambda 表达式。

**例子1:**Runnable 接口匿名实现类的对象

@Test
public void test1() {
    //创建一个Runnable接口匿名实现类的对象
    Runnable r1 = new Runnable() {
        //重写run()方法
        @Override
        public void run() {
            System.out.println("我爱Java编程!");
        }
    };
    //调用run()方法是不能启动多线程的!
    r1.run();

    System.out.println("==================================");

    //Lambda表达式的写法
    Runnable r2 = () -> System.out.println("我爱Go编程!");

    //启动多线程
    new Thread(r2).start();
}

输出:

我爱Java编程!
==================================
我爱Go编程!

**例子2:**Comparator接口匿名实现类的对象

@Test
public void test2() {
    //创建Comparator接口匿名实现类的对象
    Comparator<Integer> com1 = new Comparator<Integer>() {
        //重写compare()方法
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    int compare1 = com1.compare(2, 5);
    System.out.println(compare1);

    System.out.println("==================================");

    //Lambda表达式的写法:更简洁
    Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
    int compare2 = com2.compare(12, 5);
    System.out.println(compare2);

    System.out.println("==================================");

    //方法引用的写法:更进一步简洁
    Comparator<Integer> com3 = Integer::compare;
    int compare3 = com3.compare(-12, 5);
    System.out.println(compare3);
}

输出:

-1
==================================
1
==================================
-1

18.1.3 Lambda表达式的使用

1.对上节例子的 Lambda 表达式进行详细分析:

//Comparator接口匿名实现类的对象的普通写法
Comparator<Integer> com1 = new Comparator<Integer>() {
	//重写compare()方法
    @Override
    public int compare(Integer o1, Integer o2) {
    	return Integer.compare(o1, o2);
    }
};

//Comparator接口的Lambda表达式的写法
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
  • -> :Lambda 操作符 或 箭头操作符。
  • -> 左边:Lambda 形参列表。其实就是接口中的抽象方法的形参列表,类型省略,相当于类型推断。
  • -> 右边:Lambda 体。其实就是接口中的重写的抽象方法的方法体。

2.Lambda 表达式的使用 (分为 6 种情况介绍)

6种 Lambda 语法格式
格式一:无形参,无返回值
Runnable r1 = () -> {System.out.println(“我爱Java编程!”);};
格式二:有一个形参,无返回值
Consumer con1 = (String str) -> {System.out.println(str);};
格式三:数据类型可省略,由编译器自动进行类型推断
Consumer con1 = (str) -> {System.out.println(str);};
格式四:当只有一个形参时,参数的小括号可以省略
Consumer con1 = str -> {System.out.println(str);};
格式五:有两个或以上形参,并且有多条执行语句,并且有返回值
Comparator com1 = (x, y) -> {
System.out.println(“实现函数式接口方法!”);
return Integer.compare(x, y);
};
格式六:当 Lambda 体只有一条语句时,return 与大括号都可以省略
Comparator com1 = (x, y) -> Integer.compare(x, y);

【总结】

  • Lambda 表达式针对的都是只有一个抽象方法的接口。

  • -> 左边:lambda 形参列表的参数类型可以省略;如果 lambda 形参列表只有一个参数,其小括号()也可以省略。

  • -> 右边:lambda 体应该用一对大括号{}包裹;如果 lambda 体只有一条执行语句 (可能是 return 语句) ,则要省略这一对{}和return关键字。

3.Lambda 表达式的本质:作为接口的实例对象。

18.2 函数式(Functional)接口

18.2.1 函数式接口的概念

1.概念

如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。如下面的 Runnable 接口源码:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable 接口还被一个注解修饰:@FunctionalInterface ,说明其是函数式接口。

2.理解

以前用匿名实现类表示的现在都可以用 Lambda 表达式来写。

18.2.2 自定义函数式接口

@FunctionalInterface
public interface MyInterface {
    //只有一个抽象方法
    void method1();
}

注解 @FunctionalInterface 起到验证作用,比如,我再添加一个抽象方法,该接口就会报错。

image-20220512133604021

18.2.3 Java内置的四大核心函数式接口

函数式接口参数类型返回类型用途
Consummer消费型接口Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier供给型接口T返回类型为T的对象,包含方法:T get()
Function<T, R>函数型接口TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate断定型接口Tboolean确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t)

使用以上接口实例化时,均可以使用 Lambda 表达式。

例子1:消费显卡

@Test
public void testConsumer() {
    happyTime(2000, aDouble -> System.out.println("学习好累!买块1660Ti奖励自己,价格为:" + aDouble));
}

public void happyTime(double money, Consumer<Double> con) {
    con.accept(money);
}

输出:

学习好累!买块1660Ti奖励自己,价格为:2000.0

例子2:字符串集合过滤器

@Test
public void testPredicate() {
    List<String> list = Arrays.asList("北京", "东京", "财经", "南京", "西经", "天津");
    //Lambda表达式:Predicate接口实现类对象重写方法:如果传入的字符串中含"京",则返回true
    List<String> filter = StringFilter(list, s -> s.contains("京"));
    System.out.println(filter);
}

//筛选出特定条件的字符串
//形参list:要筛选的字符串集合;形参pre:Predicate接口实现类对象
public List<String> StringFilter(List<String> list, Predicate<String> pre) {
    //filterList:存放已筛选的字符串
    ArrayList<String> filterList = new ArrayList<>();
    //增强for循环:对传入的每个字符串,进行判别
    for (String s : list) {
        //如果满足重写接口方法的条件,则添加到filterList中
        if (pre.test(s)) {
            filterList.add(s);
        }
    }
    return filterList;
}

输出:

[北京, 东京, 南京]

18.2.4 Java内置的其他衍生函数式接口

以下函数式接口,都是由上节的四大核心函数式接口衍生出来的。也都可以使用 Lambda 表达式。

image-20220512153444754

18.3 方法引用与构造器引用

18.3.1 方法引用的概念

  • 当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是 Lambda 表达式的一个语法糖。
  • 要求:实现接口的抽象方法的参数列表返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!(针对于情况一和情况二)
  • 格式 使用操作符 “::” 将类 (或对象 ) 与方法名分隔开来。
  • 如下三种主要使用情况:
    • 对象 :: 实例方法名 (非静态方法)
    • 类 :: 静态方法名
    • 类 :: 实例方法名 (非静态方法)

18.3.2 方法引用的使用情景

1.情况一:对象::实例方法名

下面演示 Consumer 中的 void accept(T t) 引用 PrintStream 中的 void println(T t)

@Test
public void test1() {
    //Lambda表达式创建的是接口匿名实现类的对象
    Consumer<String> con1 = str -> System.out.println(str);
    con1.accept("I love Java very well");

    System.out.println("=============方法引用=============");

    //使用方法引用重写上面的代码
    //情况一:对象 :: 实例方法名 (非静态方法)
    //先创建调用对象:打印流的对象
    PrintStream ps = System.out;
    Consumer<String> con2 = ps::println;
    con2.accept("I love Go very well");
}

输出:

I love Java very well
=============方法引用=============
I love Go very well

在不使用 Lambda 表达式时,Consumer 接口中的抽象方法 void accept(T t) 中调用了打印流 PrintStream 中的 void println(T t) 方法。由于两个方法均接收一个形参,均没有返回值,且形参类型已经由泛型指定。因此可以由打印流 PrintStream 对象调用自身方法 void println(T t) 来实现 Lambda 体。

例子2:Supplier 中的 T get() 方法和 Employee 中的 String getName() 方法。

@Test
public void test2() {
    Employee e1 = new Employee(1001, "Tom", 23, 22000.0);
    Supplier<String> sup1 = () -> e1.getName();
    System.out.println(sup1.get());

    System.out.println("=============方法引用=============");

    //情况一:对象 :: 实例方法名 (非静态方法)
    Supplier<String> sup2 = e1::getName;
    System.out.println(sup2.get());
}

输出:

Tom
=============方法引用=============
Tom

Supplier 中的 T get() 方法和 Employee 中的 String getName() 方法,两个方法的形参列表和返回值类型一致,因此可以使用方法引用创建 Supplier 接口匿名实现类的对象。

2.情况二:类::静态方法名

例子1: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(2, -9));

    System.out.println("=============方法引用=============");

    //方法引用的写法
    Comparator<Integer> com2 = Integer::compare;
    System.out.println(com2.compare(-7, 1));
}

输出:

1
=============方法引用=============
-1

Comparator 中的 int compare(T t1, T t2) 和 Integer 中的 int compare(T t1, T t2) 两个方法的形参列表和返回值类型一致,因此可以采用方法引用。

例子2:Function 中的 R apply(T t) 和 Math 中的 Long round(Double d)

@Test
public void test4() {
    //非lambda表达式的写法
    Function<Double, Long> fun = new Function<Double, Long>() {
        @Override
        public Long apply(Double d) {
            return Math.round(d);
        }
    };
    System.out.println(fun.apply(6.2));

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    Function<Double, Long> fun1 = d -> Math.round(d);
    System.out.println(fun1.apply(2.69));

    System.out.println("=============方法引用=============");

    //方法引用的写法
    Function<Double, Long> fun2 = Math::round;
    System.out.println(fun2.apply(5.327));
}

输出:

6
=============Lambda表达式=============
3
=============方法引用=============
5

3.情况三:类::实例方法名 (非静态方法) 有难度

【总结】下列例子的形参列表虽然不一致,但有一个共同点:就是参数1是作为方法的调用者。也可以使用方法引用。

例子1:Comparator 中的 int compare(T t1, T t2) 方法和 String 中的 int t1.compareTo(t2) 方法。

@Test
public void test5() {
    //非lambda表达式的写法
    Comparator<String> com = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    };
    System.out.println(com.compare("java", "java"));

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
    System.out.println(com1.compare("Go", "Get"));

    System.out.println("=============方法引用=============");

    //方法引用的写法
    Comparator<String> com2 = String::compareTo;
    System.out.println(com2.compare("Tom", "Tim"));
}

输出:

0
=============Lambda表达式=============
10
=============方法引用=============
6

例子2:BiPredicate 中的 boolean test(T t1, T t2) 方法和 String 中的 boolean t1.equals(t2) 方法。

@Test
public void test6() {
    //非lambda表达式的写法
    BiPredicate<String, String> bp = new BiPredicate<String, String>() {
        @Override
        public boolean test(String s1, String s2) {
            return s1.equals(s2);
        }
    };
    System.out.println(bp.test("java", "java"));

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    BiPredicate<String, String> bp1 = (s1, s2) -> s1.equals(s2);
    System.out.println(bp1.test("abc", "abd"));

    System.out.println("=============方法引用=============");

    //方法引用的写法
    BiPredicate<String, String> bp2 = String::equals;
    System.out.println(bp2.test("Go", "Go"));
}

输出:

true
=============Lambda表达式=============
false
=============方法引用=============
true

例子3:Function 中的 R apply(T t) 方法和 Employee 中的 String getName() 方法。

@Test
public void test7() {
    Employee e1 = new Employee(1001, "Tom", 23, 22000.0);

    //非lambda表达式的写法
    Function<Employee, String> fun = new Function<Employee, String>() {
        @Override
        public String apply(Employee e) {
            return e.getName();
        }
    };
    System.out.println(fun.apply(e1));

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    Function<Employee, String> fun1 = e -> e.getName();
    System.out.println(fun1.apply(e1));

    System.out.println("=============方法引用=============");

    //方法引用的写法
    Function<Employee, String> fun2 = Employee::getName;
    System.out.println(fun2.apply(e1));
}

输出:

Tom
=============Lambda表达式=============
Tom
=============方法引用=============
Tom

18.3.3 构造器引用

【总结】和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形成列表一致;抽象方法的返回值即为构造器所属的类的类型。

例子1:Supplier 中的 T get()

【总结】Supplier 中的 T get() 方法没有形参列表,有返回值;而 Employee 的空参构造器同样没有形参列表,同样返回一个 Employee 类的对象。因此两个结构在形成列表和返回值上是一致的,因此可以使用构造器引用。

@Test
public void test1() {
    //非lambda表达式的写法
    Supplier<Employee> sup = new Supplier<Employee>() {
        @Override
        public Employee get() {
            return new Employee(1001, "Tom", 23, 22000.0);
        }
    };
    System.out.println(sup.get());

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    Supplier<Employee> sup1 = () -> new Employee();//调用了空参构造器
    System.out.println(sup1.get());

    System.out.println("=============构造器引用=============");

    //构造器引用的写法
    Supplier<Employee> sup2 = Employee::new;//调用了空参构造器
    System.out.println(sup2.get());
}

输出:

Employee{id=1001, name='Tom', age=23, salary=22000.0}
=============Lambda表达式=============
Employee{id=0, name='null', age=0, salary=0.0}
=============构造器引用=============
Employee{id=0, name='null', age=0, salary=0.0}

例子2:Function 中的 R apply(T t)

@Test
public void test2() {
    //非lambda表达式的写法
    Function<Integer, Employee> fun = new Function<Integer, Employee>() {
        @Override
        public Employee apply(Integer id) {
            return new Employee(id);
        }
    };
    System.out.println(fun.apply(23));//调用Employee(int id)构造器

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    Function<Integer, Employee> fun1 = id -> new Employee(id);//调用Employee(int id)构造器
    System.out.println(fun1.apply(12));

    System.out.println("=============构造器引用=============");

    //构造器引用的写法
    Function<Integer, Employee> fun2 = Employee::new;//调用Employee(int id)构造器
    System.out.println(fun2.apply(35));
}

输出:

Employee{id=23, name='null', age=0, salary=0.0}
=============Lambda表达式=============
Employee{id=12, name='null', age=0, salary=0.0}
=============构造器引用=============
Employee{id=35, name='null', age=0, salary=0.0}

例子3:BiFunction 中的 R apply(T t, U u)

@Test
public void test3() {
    //非lambda表达式的写法
    BiFunction<Integer, String, Employee> bf = new BiFunction<Integer, String, Employee>() {
        @Override
        public Employee apply(Integer id, String name) {
            return new Employee(id, name);//调用Employee(int id, String name)构造器
        }
    };
    System.out.println(bf.apply(1001, "Tom"));

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    BiFunction<Integer, String, Employee> bf1 = (id, name) -> new Employee(id, name);
    System.out.println(bf1.apply(1002, "Jerry"));

    System.out.println("=============构造器引用=============");

    //构造器引用的写法
    BiFunction<Integer, String, Employee> bf2 = Employee::new;
    System.out.println(bf2.apply(1003, "Dick"));
}

输出:

Employee{id=1001, name='Tom', age=0, salary=0.0}
=============Lambda表达式=============
Employee{id=1002, name='Jerry', age=0, salary=0.0}
=============构造器引用=============
Employee{id=1003, name='Dick', age=0, salary=0.0}

18.3.4 数组引用

【总结】可以把数组看作是一个特殊的类,其本质仍然是构造器引用。

例子:Function 中的 R apply(T t)

@Test
public void test4() {
    //非lambda表达式的写法
    Function<Integer, String[]> fun = new Function<Integer, String[]>() {
        @Override
        public String[] apply(Integer cap) {
            return new String[cap];
        }
    };
    System.out.println(Arrays.toString(fun.apply(5)));

    System.out.println("=============Lambda表达式=============");

    //Lambda表达式的写法
    Function<Integer, String[]> fun1 = cap -> new String[cap];
    System.out.println(Arrays.toString(fun1.apply(2)));


    System.out.println("=============数组引用=============");

    //数组引用的写法
    Function<Integer, String[]> fun2 = String[]::new;
    System.out.println(Arrays.toString(fun2.apply(3)));
}

输出:

[null, null, null, null, null]
=============Lambda表达式=============
[null, null]
=============数组引用=============
[null, null, null]

18.4 强大的Stream API

Java 8 中最重大的两个新特性:其一是 Lambda 表达式;另一个则是 Stream API 。

18.4.1 Stream API概述

  • Stream API ( java.util.stream) 把真正的函数式编程风格引入到 Java 中。这是目前为止对 Java 类库最好的补充,因为 Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。
  • Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

2.为什么要使用 Stream API

  • 实际开发中,项目中多数数据源都来自于 Mysql 、Oracle 等。但现在数据源可以更多了,有 MongoDB 、Radis 等,而这些 NoSQL 的数据就需要 Java 层面去处理。
  • Stream 和 Collection 集合的区别: Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中;后者主要是面向 CPU,通过 CPU 实现计算。

3.什么是 Stream

  • 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据;Stream 讲的是计算!”

4.注意

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream 。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

5.Stream 的操作三个步骤

  • 创建 Stream。一个数据源 (如:集合、数组) ,获取一个流。
  • 中间操作。一个中间操作链,对数据源的数据进行处理。
  • 终止操作 (终端操作) 。一旦执行终止操作,就执行中间操作链,并产生结果。之后就不会再被使用。

image-20220514144727787

18.4.2 Stream的实例化

1.创建方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的默认方法:

方法作用
default Stream stream()返回一个顺序流
default Stream parallelStream()返回一个并行流

注:default 是 Java8 中接口的新特性,只要是接口的实现类对象,都可以直接调用接口的默认方法,而无需在实现类中重写该方法 (也可以重写)。

@Test
public void test1() {
    //导入老师提供的数据
    List<Employee> employees = EmployeeData.getEmployees();

    //default Stream<E> stream():返回一个顺序流
    Stream<Employee> stream = employees.stream();

    //default Stream<E> parallelStream():返回一个并行流
    Stream<Employee> pStream = employees.parallelStream();
}

2.创建方式二:通过数组

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

方法作用
static Stream stream(T[] array)返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

image-20220514151246028

@Test
public void test2() {
    int[] arr = {1, 2, 3, 4, 5};
    //调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流
    IntStream stream = Arrays.stream(arr);

    Employee e1 = new Employee(1001, "Tom");
    Employee e2 = new Employee(1002, "Jerry");
    Employee[] employees = {e1, e2};
    Stream<Employee> stream1 = Arrays.stream(employees);
}

3.创建方式三:通过Stream的of()

可以调用 Stream 类静态方法 of() ,通过显示值创建一个流。它可以接收任意数量的参数。

方法作用
public static Stream of(T… values)返回一个流
@Test
public void test3() {
    //调用Stream的静态方法public static <T> Stream<T> of(T... values):返回一个流
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}

4.创建方式四:创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate() ,创建无限流。

方法作用
public static Stream generate(Supplier s)生成
public static Stream iterate(final T seed, final UnaryOperator f)迭代
@Test
public void test4() {
    //生成: public static <T> Stream<T> generate(Supplier<T> s)
    //生成10个随机数,注意要加limit限制,否则一直循环
    Stream.generate(Math::random).limit(5).forEach(System.out::println);

    //迭代:public static <T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
    //遍历前10个偶数,注意要加limit限制,否则一直循环
    Stream.iterate(0, t -> t + 2).limit(5).forEach(System.out::println);
}

输出:

0.867230965959081
0.4220369796954957
0.1817389963025311
0.388150897485273
0.3247770613812402
0
2
4
6
8

18.4.3 Stream的中间操作

多个中间操作可以连接起来形成一个流水线 (通过不断地”点“出来),除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” (让我想起懒汉式的单例模式和 JDK 8 中 Map 的创建过程等到 add() 才创建数组) 。

1.筛选与切片

方法作用
filter(Predicate p)接收 Lambda ,从流中排除某些元素
distinct()去重,通过流所生成的元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补

例子

数据:EmployeeData 中的数据:

public class EmployeeData {
    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();

        list.add(new Employee(1001, "马化腾", 34, 6000.38));
        list.add(new Employee(1002, "马云", 12, 9876.12));
        list.add(new Employee(1003, "刘强东", 33, 3000.82));
        list.add(new Employee(1004, "雷军", 26, 7657.37));
        list.add(new Employee(1005, "李彦宏", 65, 5555.32));
        list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
        list.add(new Employee(1007, "任正非", 26, 4333.32));
        list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
        list.add(new Employee(1008, "扎克伯格", 35, 2500.32));//重复数据

        return list;
    }
}

【注意】一旦执行终止操作,就执行中间操作链,并产生结果。stream 之后就不会再被使用。要想再使用 stream 执行中间操作,只能重新创建 stream 对象。

@Test
public void test1() {
    //通过集合实例化Stream
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<Employee> stream = employees.stream();

    //filter(Predicate p): 查询员工表中年龄小于30的员工信息
    stream.filter(e -> e.getAge() < 30).forEach(System.out::println);

    System.out.println("=========================================================");
    //limit(long maxSize):截断流,使其元素不超过给定数量
    employees.stream().limit(3).forEach(System.out::println);

    System.out.println("=========================================================");
    //skip(long n): 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
    employees.stream().skip(5).forEach(System.out::println);

    System.out.println("=========================================================");
    //distinct():去重,通过流所生成的元素的hashCode()和equals()去除重复元素
    employees.stream().distinct().forEach(System.out::println);
}

输出:

Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
=========================================================
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
=========================================================
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
=========================================================
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}

2.映射

方法作用
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

例子1:map() 的用法

@Test
public void test2() {
    //通过集合实例化Stream
    List<String> list = Arrays.asList("aa", "bb", "cc");
    Stream<String> stream = list.stream();

    //map(Function f)
    stream.map(String::toUpperCase).forEach(System.out::println);

    //获取员工姓名长度大于3的员工的姓名
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<String> namesStream = employees.stream().map(Employee::getName);
    namesStream.filter(name -> name.length() > 3).forEach(System.out::println);
}

输出:

AA
BB
CC
比尔盖茨
扎克伯格

例子2:flatMap() 的用法

flatMap() 能把 Stream 里面的 Stream 元素都打开,连接成新的 Stream 。用集合作类比,相当于:

[[1,2], [3,4], [5,6]] -> [1,2,3,4,5,6]
@Test
public void test2() {
    //通过集合实例化Stream
    List<String> list = Arrays.asList("aa", "bb", "cc");
    Stream<String> stream = list.stream();
    
    //对比和map的区别体会flatMap()的用法
    //此时是Stream里面的元素还是Stream,相当于集合里面元素是集合
    Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::stringToStream);
    //逐一输出Stream里面的Stream的元素(相当于二维数组的嵌套循环)
    streamStream.forEach(s -> {
        s.forEach(System.out::println);
    });

    System.out.println("==================================");
    //flatMap(Function f)对比上面map,优势就能展现出来了,相当于集合中的`addAll()`方法
    Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::stringToStream);
    characterStream.forEach(System.out::println);
}

//把字符串中的每个字符构成的集合list转换成对应的Stream的流
public static Stream<Character> stringToStream(String str) {
	ArrayList<Character> list = new ArrayList<>();
    for (Character c : str.toCharArray()) {
    	list.add(c);
    }
    return list.stream();
}

输出:

a
a
b
b
c
c
==================================
a
a
b
b
c
c

3.排序

方法作用
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序

例子

//3.排序
@Test
public void test3() {
    //自然排序
    List<Integer> list = Arrays.asList(12, -48, 65, 34, -87, 0, 7);
    list.stream().sorted().forEach(System.out::println);

    //定制排序: 先按员工的年龄从小到大排序,再按员工的薪资从大到小排序
    List<Employee> employees = EmployeeData.getEmployees();
    //创建Comparator接口实现类的对象,可以写成Lambda表达式
    Comparator<Employee> com = new Comparator<Employee>() {
        @Override
        public int compare(Employee e1, Employee e2) {
            int ageCom = Integer.compare(e1.getAge(), e2.getAge());
            if (ageCom == 0) {
                return -Double.compare(e1.getSalary(), e2.getSalary());
            }
            return ageCom;
        }
    };
    employees.stream().sorted(com).forEach(System.out::println);

    System.out.println("=========================Lambda表达式写法=========================");
    //Lambda表达式写法
    employees.stream().sorted((e1, e2) -> {
        int ageCom = Integer.compare(e1.getAge(), e2.getAge());
        if (ageCom == 0) {
            return -Double.compare(e1.getSalary(), e2.getSalary());
        }
        return ageCom;
    }).forEach(System.out::println);
}

输出:

-87
-48
0
7
12
34
65
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
=========================Lambda表达式写法=========================
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}

18.4.4 Stream的终止操作

  • 终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、 Integer,甚至是 void 。
  • 流进行了终止操作后,不能再次使用。

1.匹配与查找

方法作用
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代 (使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代,它帮你把迭代做了)

例子

@Test
public void test1() {
    //allMatch(Predicate p)
    //练习:是否所有员工的年龄都大于18岁?
    List<Employee> employees = EmployeeData.getEmployees();
    boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
    System.out.println("是否所有员工的年龄都大于18岁: " + allMatch);

    //anyMatch(Predicate p)
    //练习:是否存在员工的工资大于10000?
    boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
    System.out.println("是否存在员工的工资大于10000元: " + anyMatch);

    //noneMatch(Predicate p)
    //练习:是否没有员工姓“雷”?
    boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
    System.out.println("是否没有员工姓“雷”: " + noneMatch);

    //findFirst(): 通常是排序完成后,获取第一个元素
    Optional<Employee> firstE = employees.stream().findFirst();
    System.out.println("第一位员工: " + firstE);

    //findAny()
    Optional<Employee> anyE = employees.parallelStream().findAny();
    System.out.println("获取任意员工:" + anyE);

    //count()
    long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
    System.out.println("员工工资大于5k的总人数:" + count);

    //max(Comparator c)
    //练习:返回最高工资
    Optional<Double> maxSalary = employees.stream().map(Employee::getSalary).max(Double::compare);
    System.out.println("最高工资: " + maxSalary);

    //min(Comparator c)
    //练习:返回最低工资的员工
    Optional<Employee> minEmployee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
    System.out.println("最低工资的员工: " + minEmployee);

    System.out.println("===============forEach()使用例子===============");
    //forEach(Consumer c)——内部迭代
    employees.stream().forEach(System.out::println);
    //使用集合的遍历操作——外部迭代
    employees.forEach(System.out::println);
}

输出:

是否所有员工的年龄都大于18岁: false
是否存在员工的工资大于10000元: false
是否没有员工姓“雷”: false
第一位员工: Optional[Employee{id=1001, name='马化腾', age=34, salary=6000.38}]
获取任意员工:Optional[Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}]
员工工资大于5k的总人数:5
最高工资: Optional[9876.12]
最低工资的员工: Optional[Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}]
===============forEach()使用例子===============
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1003, name='刘强东', age=33, salary=3000.82}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1007, name='任正非', age=26, salary=4333.32}
Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}

2.归约

方法作用
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional

例子

@Test
public void test2() {
    //reduce(T iden, BinaryOperator b)
    //练习:计算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("1~10的自然数之和: " + sum);

    //reduce(BinaryOperator b)
    //练习:计算公司所有员工工资总和
    List<Employee> employees = EmployeeData.getEmployees();
    Optional<Double> salarySum = employees.stream().map(Employee::getSalary).reduce(Double::sum);
    //手动写和
//    Optional<Double> salarySum = employees.stream().map(Employee::getSalary).reduce((d1, d2) -> d1 + d2);
    System.out.println("公司所有员工工资总和: " + salarySum);
}

输出:

1~10的自然数之和: 55
公司所有员工工资总和: Optional[48424.08]

3.收集

方法作用
collect(Collector c)将流转换成其他形式。接收一个Collector接口的实现,用于给Stream中的元素做汇总的方法
  • Collector 接口中方法的实现决定了如何对流执行收集的操作 (如收集到 List 、 Set 、Map) 。
  • 另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

image-20220515165842711

image-20220515165902071

例子

@Test
public void test3() {
    //collect(Collector c)
    //练习:查找工资大于6000的员工,结果返回为一个List或Set
    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> list = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
    list.forEach(System.out::println);

    System.out.println("=======================Set=======================");
    //返回Set
    Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
    employeeSet.forEach(System.out::println);
}

输出:

Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1004, name='雷军', age=26, salary=7657.37}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
=======================Set=======================
Employee{id=1001, name='马化腾', age=34, salary=6000.38}
Employee{id=1002, name='马云', age=12, salary=9876.12}
Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
Employee{id=1004, name='雷军', age=26, salary=7657.37}

18.5 Optional类

18.5.1 Optional类概述

  • 到目前为止,臭名昭著的空指针异常是导致 Java 应用程序失败的最常见原因。以前,为了解决空指针异常, Google 公司著名的 Guava 项目引入了 Optional 类,Guava 通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到 Google Guava 的启发,Optional 类已经成为 Java 8 类库的一部分。
  • Optional 类 (java.util.Optional) 是一个容器类 它可以保存类型 T 的值, 代表这个值存在。或者仅仅保存 null,表示这个值不存在 。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

【总结】Optional 类是为了在程序中避免出现空指针异常而创建的。

18.5.2 创建Optional类

方法描述
Optional.of(T t)创建一个Optional实例,t必须非空
Optional.empty()创建一个空的Optional实例
Optional.ofNullable(T t)t可以为null

例子

① Girl 类:

public class Girl {
    private String name;

    public Girl() {
    }

    public Girl(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                '}';
    }
}

② Boy 类:

public class Boy {
    private Girl girl;

    public Boy() {
    }

    public Boy(Girl girl) {
        this.girl = girl;
    }

    public Girl getGirl() {
        return girl;
    }

    public void setGirl(Girl girl) {
        this.girl = girl;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "girl=" + girl +
                '}';
    }
}
@Test
public void test1() {
    //Optional.of(T t): 创建一个Optional实例,t必须非空
    Girl girl = new Girl();
    Optional<Girl> optionalGirl = Optional.of(girl);
    System.out.println(optionalGirl);

    //Optional.ofNullable(T t): t可以为null
    girl = null;
    Optional<Girl> optionalGirl1 = Optional.ofNullable(girl);
    System.out.println(optionalGirl1);
}

输出:

Optional[Girl{name='null'}]
Optional.empty

18.5.3 判断Optional容器中是否包含对象

方法描述
boolean isPresent()判断是否包含对象
void ifPresent(Consumer<? super T> consumer)如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它

例子

public String getGirlName(Boy boy) {
    return boy.getGirl().getName();
}

//没有使用Optional类的情况
@Test
public void test2() {
    Boy boy = new Boy();
    String girlName = getGirlName(boy);
    System.out.println(girlName);//空指针异常
}

输出:

java.lang.NullPointerException

使用了Optional类的情况:

//使用Optional类优化以后的getGirlName()
public String getGirlName1(Boy boy) {
    Optional<Boy> optionalBoy = Optional.ofNullable(boy);
    //此时boy1一定非空
    Boy boy1 = optionalBoy.orElse(new Boy(new Girl("迪丽热巴")));
    Girl girl = boy1.getGirl();
    Optional<Girl> optionalGirl = Optional.ofNullable(girl);
    //此时Girl1一定非空
    Girl girl1 = optionalGirl.orElse(new Girl("古力娜扎"));
    return girl1.getName();
}

@Test
public void test3() {
    Boy boy = null;
    String girlName = getGirlName1(boy);
    System.out.println(girlName);

    boy = new Boy();
    String girlName1 = getGirlName1(boy);
    System.out.println(girlName1);

    Boy boy1 = new Boy(new Girl("王冰冰"));
    String girlName2 = getGirlName1(boy1);
    System.out.println(girlName2);
}

输出:

迪丽热巴
古力娜扎
王冰冰

18.5.4 获取Optional容器的对象

方法描述
T get()如果调用对象包含值,返回该值,否则抛异常
T orElse(T other)如果有值则将其返回,否则返回指定的other对象
T orElseGet(Supplier<? extends T> other)如果有值则将其返回,否则返回由Supplier接口实现提供的对象
T orElseThrow(Supplier<? extends X> exceptionSupplier)如果有值则将其返回,否则抛出由Supplier接口实现提供的异常

例子:

@Test
public void test1() {
    Girl girl = new Girl();
    //T orElse(T other): 如果有值则将其返回,否则返回指定的other对象
    girl = null;
    Optional<Girl> optionalGirl = Optional.ofNullable(girl);
    //无值返回指定的girl赵丽颖
    Girl girl1 = optionalGirl.orElse(new Girl("赵丽颖"));
    System.out.println(optionalGirl);
    System.out.println(girl1);
}

输出:

Optional.empty
Girl{name='赵丽颖'}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐