十八、 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
起到验证作用,比如,我再添加一个抽象方法,该接口就会报错。
18.2.3 Java内置的四大核心函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consummer消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Supplier供给型接口 | 无 | T | 返回类型为T的对象,包含方法:T get() |
Function<T, R>函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
Predicate断定型接口 | T | boolean | 确定类型为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 表达式。
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。一个数据源 (如:集合、数组) ,获取一个流。
- 中间操作。一个中间操作链,对数据源的数据进行处理。
- 终止操作 (终端操作) 。一旦执行终止操作,就执行中间操作链,并产生结果。之后就不会再被使用。
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)
@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 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
例子:
@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='赵丽颖'}