第17章 Java8其他新特性
学习目标
- 能够理解函数式编程相对于面向对象的优点
- 理解函数式接口概念
- 了解@FunctionalInterface注解
- 能够掌握Lambda表达式的标准格式
- 能够掌握Lambda表达式的省略格式与规则
- 能够使用Supplier函数式接口
- 能够使用Consumer函数式接口
- 能够使用Function函数式接口
- 能够使用Predicate函数式接口
- 能够自定义函数式接口并使用Lambda表达式使用它
- 能够使用方法引用和构造器引用
- 能够理解流与集合相比的优点
- 能够理解流的延迟执行特点
- 能够通过集合、映射或数组获取流
- 能够掌握常用的流操作
- 能够使用Optional类包装对象并获取其中包装的对象
Java8的新特征有很多,之前我们在学习接口时,学习了接口的静态方法和默认方法,在学习常用类时,学习了新版的日期时间API。
今天我们来学习Java8最具革命性的两个新特性:Lambda表达式和Stream流。
17.1 Lambda表达式
Lambda表达式是JDK1.8之后的一种语法。Lambda表达式本质上是⼀个匿名⽅法。Lambda允许把函数作为⼀个⽅法的参数(函数作为参数传递进⽅法中)或者把代码看成数据。使⽤Lambda 表达式可以使代码变的更加简洁紧凑,使Java的语言表达能力得到了提升。
17.1.1 Lambda入门示例
需求:开启一个线程,完成一个任务,任务是在控制台输出一句话"革命尚未成功,同志还需努力"
实现方式一:自定义Runnable接口实现类
package com.atguigu.lambda;
public class TestLambda {
public static void main(String[] args) {
// 方式一:自定义Runnable接口实现类来定义任务内容,并使用Thread类来启动该线程
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("革命尚未成功,同志还需努力11111111");
}
}
实现方式二:匿名内部类方式
package com.atguigu.lambda;
public class TestLambda {
public static void main(String[] args) {
// 方式一:自定义Runnable接口实现类来定义任务内容,并使用Thread类来启动该线程
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
// 方式二,创建一个Runnable匿名内部类来定义任务内容,并使用Thread类来启动该线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("革命尚未成功,同志还需努力22222222");
}
}).start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("革命尚未成功,同志还需努力11111111");
}
}
实现方式三:通过Lambda方式
package com.atguigu.lambda;
public class TestLambda {
public static void main(String[] args) {
// 方式三:通过Lambda方式简化Runnable实现类,并使用Thread类来启动该线程
new Thread(() -> System.out.println("革命尚未成功,同志还需努力33333")).start();
}
}
代码分析:
面向对象思想
对于Runnable
的匿名内部类用法,可以分析出几点内容:
-
Thread
类需要Runnable
接口作为参数,其中的抽象run
方法是用来指定线程任务内容的核心; -
为了指定
run
的方法体,不得不需要Runnable
接口的实现类; -
为了省去定义一个
RunnableImpl
实现类的麻烦,不得不使用匿名内部类; -
必须覆盖重写抽象
run
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; -
而实际上,似乎只有方法体才是关键所在。
-
在当前需求中,我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将
run
方法体内的代码传递给Thread
类知晓。传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。
编程思想转换——当前需求中,我们更在意做什么,怎么做,而不是"谁来做"。
17.1.2 函数式编程思想
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbaVV9oD-1680573818944)(尚硅谷-JavaSE-第17章 Java8新特性-陈叶.assets/03-Overview.png)]
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”。编程中的函数,也有类似的概念,你调用我的时候,给我实参为形参赋值,然后通过运行方法体,给你返回一个结果。对于调用者来做,关注这个方法具备什么样的功能。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
-
面向对象的思想:
- 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
-
函数式编程思想:
- 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
Java8引入了Lambda表达式之后,Java也开始支持函数式编程。
Lambda表达式不是Java最早使用的,很多语言就支持Lambda表达式,例如:C++,C#,Python,Scala等。如果有Python或者Javascript的语言基础,对理解Lambda表达式有很大帮助,可以这么说lambda表达式其实就是实现SAM接口的语法糖,使得Java也算是支持函数式编程的语言。Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的(啰嗦的)匿名内部类。
备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。
刚才案例中的方式三代码和方式一、二的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!
17.1.3 Lambda表达式语法
17.1.3.1 Lambda表达式语法格式及说明
(形参列表) -> {Lambda体}
语法格式说明:
- (形参列表)它就是你要赋值的函数式接口的抽象方法的(形参列表),照抄
- {Lambda体}就是实现这个抽象方法的方法体
- ->称为Lambda操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式)
以一个Runnable为例,Lambda简化过程:
Lambda简化过程
第一步,去掉匿名内部类的声明
第二步,去掉方法名和返回值
第三步,参数与作用域{}之间加上->
第四步,去掉参数数据类型(自动推断类型)
17.1.3.2 使用Lambda表达式语法示例
需求:实现一个字符串数组的排序,要求字符串排序过程中忽略字母的大小写
例:字符串数组为{"xx","YY","BB","aa"}
排序后结果为:{"aa","bb","xx","YY"}
实现方式一:匿名内部类
package com.atguigu.lambda;
import java.util.Arrays;
import java.util.Comparator;
/**
* @author Sarah
* @description 练习Lambda表达式的标准语法
* 需求:实现一个字符串数组的排序,要求字符串排序过程中忽略字母的大小写
* 例:字符串数组为{"xx","YY","BB","aa"}
* 排序后结果为:{"aa","bb","xx","YY"}
* 分析:字符串默认的排序方式是按照字母的ascii码表值排序,
* 所以,需要自定义排序(忽略大小写)
* Arrays.sort(arr,Comparator接口)
*
* Lambda表达式标准语法
* (形参列表) -> {Lambda体}
*/
public class LambdaDemo02 {
public static void main(String[] args) {
// 1.定义一个字符串数组
String[] arr = {"xx","YY","BB","aa"};
// 2.对字符串按照要求进行排序(忽略大小写)
// 2.1 使用匿名内部类方式实现
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
// 3.将排序后的字符串数组输出
System.out.println(Arrays.toString(arr));
}
}
实现方式二:Lambda表达式
package com.atguigu.lambda;
import java.util.Arrays;
import java.util.Comparator;
/**
* @author Sarah
* @description 练习Lambda表达式的标准语法
* 需求:实现一个字符串数组的排序,要求字符串排序过程中忽略字母的大小写
* 例:字符串数组为{"xx","YY","BB","aa"}
* 排序后结果为:{"aa","bb","xx","YY"}
* 分析:字符串默认的排序方式是按照字母的ascii码表值排序,
* 所以,需要自定义排序(忽略大小写)
* Arrays.sort(arr,Comparator接口)
*
* Lambda表达式标准语法
* (形参列表) -> {Lambda体}
*/
public class LambdaDemo02 {
public static void main(String[] args) {
// 1.定义一个字符串数组
String[] arr = {"xx","YY","BB","aa"};
// 2.对字符串按照要求进行排序(忽略大小写)
// 2.1 使用匿名内部类方式实现
/*Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});*/
// 2.2 使用Lambda方式实现
// 2.2.1 Lambda表达式标准语法形式
// Arrays.sort(arr, (String o1, String o2)->{return o1.compareToIgnoreCase(o2);});
// 2.2.2 Lambda表达式中参数的数据类型可以省略(可自行推断)
// Arrays.sort(arr, ( o1, o2)-> {return o1.compareToIgnoreCase(o2);} );
// 2.2.3 Lambda体中如果只有一行代码,则可以省略{;}
// 如果省略{}时有return关键字,则return也必须一并省略
Arrays.sort(arr, (o1, o2)-> o1.compareToIgnoreCase(o2));
// 3.将排序后的字符串数组输出
System.out.println(Arrays.toString(arr));
}
}
Lambda表达式的简化说明
某些情况下Lambda表达式可以精简:
- 当{Lambda体}中只有一句语句时,可以省略{}和{;}
- 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{}、return、;三者可以省略。它们三要么一起省略,要么都不省略。
- 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
- 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
- 当Lambda表达式(形参列表)是空参时,()不能省略
17.1.3.4 Lambda的应用场景
是不是所有的接口实例都能使用Lambda表达式进行替代呢?
实例:一个接口里面有多个抽象方法
代码演示
package com.atguigu;
public class Demo3 {
public static void main(String[] args) {
ClassBB bb = new ClassBB();
/*bb.show(new InterAA() {
@Override
public void method1() {
System.out.println("aaa");
}
@Override
public void method2() {
System.out.println("bbb");
}
});*/
bb.show(() -> { System.out.println("aaa"); });
}
}
class ClassBB{
public void show(InterAA aa){
aa.method1();
}
}
interface InterAA{
void method1();
// void method2();
}
答案:不是,并不是所有的接口实例都可以使用Lambda表达式进行替代,
寻找Runnable和Comparator的规律
17.1.4 函数式接口
17.1.4.1 函数接口的概念
lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。
其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
17.1.4.2 盘点之前学过的接口
之前学过的接口已经很多了:Cloneable、Comparable、Comparator、Runnable、Iterable、Iterator、Collection、List、Set、Map、Serializable等。
上述接口中,满足SAM接口特点的有:
- java.lang.Runnable
- public void run()
- java.lang.Iterable
- public Iterator iterate()
- java.lang.Comparable
- public int compareTo(T t)
- java.util.Comparator
- public int compare(T t1, T t2)
上述SAM接口中,标记了@FunctionalInterface注解有:Runnable,Comparator。
结论:
Lambda表达式就是当做一个【函数式接口】的实例 用来给的变量或形参赋值用的。
总结
Lambda表达式
本质: 可以当做函数式接口的实例
一个实例的基本用法
用法
当做 参数实例
当做 返回值实例
Lambda表达式的简化说明
某些情况下Lambda表达式可以精简:
- 当{Lambda体}中只有一句语句时,可以省略{}和{;}
- 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{}、return、;三者可以省略。它们三要么一起省略,要么都不省略。
- 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
- 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
- 当Lambda表达式(形参列表)是空参时,()不能省略
17.1.4.3 java.util.function包四大类函数式接口
Java8在java.util.function新增了很多函数式接口:主要分为四大类,供给型、消费型、判断型、功能型。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口。
体验函数式接口的四大类型
1.消费型接口 Comsumer 特点:有参数,无返回值
void accept(T t) 接收一个对象用于完成功能
2.供给型接口 Supplier 特点:无参数,有返回值
T get() 返回一个对象
3.判断型接口 Predicate 特点:有参数,返回值类型为boolean
boolean test(T t) 接收一个对象,返回值为boolean类型
4. 功能型接口 Function/UnaryOperator 特点:有参数,有返回值
R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
其中UnaryOperator接口的参数类型和返回值类型需一致
消费型接口的抽象方法特点:有形参,但是返回值类型是void
接口名 | 抽象方法 | 描述 |
---|---|---|
Consumer | void accept(T t) | 接收一个对象用于完成功能 |
BiConsumer<T,U> | void accept(T t, U u) | 接收两个对象用于完成功能 |
DoubleConsumer | void accept(double value) | 接收一个double值 |
IntConsumer | void accept(int value) | 接收一个int值 |
LongConsumer | void accept(long value) | 接收一个long值 |
ObjDoubleConsumer | void accept(T t, double value) | 接收一个对象和一个double值 |
ObjIntConsumer | void accept(T t, int value) | 接收一个对象和一个int值 |
ObjLongConsumer | void accept(T t, long value) | 接收一个对象和一个long值 |
消费型接口使用示例:
JDK1.8中java.lang.Iterable接口中增加了一个默认方法
因为Collection接口继承了Iterable接口,这就意味着所有Collection系列的接口都包含该方法。
public default void forEach(Consumer<? super T> consumer) 该方法功能是遍历Collection集合,并将传递给consumer参数的操作代码应用在每一个元素上
forEach方法的功能:遍历集合拿到每一个元素,并对元素做操作
具体什么操作取决于参数列表中Comsumer接口中的accpet()的定义
package com.atguigu.lambda;
import java.util.ArrayList;
/**
* @author Sarah
* @description 体验函数式接口的四大类型
* 1.消费型接口 Comsumer 特点:有参数,无返回值
* void accept(T t) 接收一个对象用于完成功能
*/
public class FunntionInterfaceDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("atguigu");
list.add("sarah");
// forEach方法的功能:遍历集合拿到每一个元素,并对元素做操作
// 具体什么操作取决于参数列表中Comsumer接口中的accpet()的定义
list.forEach(s -> System.out.println(s));
}
}
Supplier接口是一个供给型的接口,本质就是一个容器,可以用来存储数据(或者是产生数据的规则),然后可以供其他方法使用的这么一个接口
供给型接口的抽象方法特点:无参,但是有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
Supplier | T get() | 返回一个对象 |
BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
DoubleSupplier | double getAsDouble() | 返回一个double值 |
IntSupplier | int getAsInt() | 返回一个int值 |
LongSupplier | long getAsLong() | 返回一个long值 |
供给型接口使用示例:
测试类:
package com.atguigu.lambda;
import com.atguigu.bean.Student;
import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;
/**
* @author Sarah
* @description 体验函数式接口的四大类型
* 1.消费型接口 Comsumer 特点:有参数,无返回值
* void accept(T t) 接收一个对象用于完成功能
* 2.供给型接口 Supplier 特点:无参数,有返回值
* T get() 返回一个对象
*/
public class FunntionInterfaceDemo01 {
public static void main(String[] args) {
/*Supplier<Integer> supplier1 = new Supplier<Integer>() {
@Override
public Integer get() {
return new Random().nextInt(100);
}
};
System.out.println(supplier1.get());*/
Supplier<Integer> supplier1 = () -> new Random().nextInt();
System.out.println(supplier1.get());
Supplier<String> supplier2 = () ->"尚硅谷";
System.out.println(supplier2.get());
Supplier<Student> supplier3 = () ->new Student("张三",23);
System.out.println(supplier3.get());
}
}
Student类
package com.atguigu.bean;
/**
* @author Sarah
* @description 学生类
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
判断型接口的抽象方法特点:有参,返回值类型是boolean结果。
接口名 | 抽象方法 | 描述 |
---|---|---|
Predicate | boolean test(T t) | 接收一个对象 |
BiPredicate<T,U> | boolean test(T t, U u) | 接收两个对象 |
DoublePredicate | boolean test(double value) | 接收一个double值 |
IntPredicate | boolean test(int value) | 接收一个int值 |
LongPredicate | boolean test(long value) | 接收一个long值 |
判断型接口使用示例:
JDK1.8时,Collecton<E>接口增加了一下方法,其中一个如下:
public default boolean removeIf(Predicate<? super E> predicate)用于删除集合中满足predicate指定的条件判断的。
具体哪些元素符合条件取决于参数列表中Predcate接口中的test()的定义
package com.atguigu.lambda;
import com.atguigu.bean.Student;
import java.util.ArrayList;
import java.util.Random;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* @author Sarah
* @data 2022/6/9 11:14
* @description 体验函数式接口的四大类型
* 1.消费型接口 Comsumer 特点:有参数,无返回值
* void accept(T t) 接收一个对象用于完成功能
* 2.供给型接口 Supplier 特点:无参数,有返回值
* T get() 返回一个对象
* 3.判断型接口 Predcate 特点:有参数,返回值类型为boolean
* boolean test(T t) 接收一个对象
*/
public class FunntionInterfaceDemo01 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("atgui");
list.add("sarah");
// 集合原数据
System.out.println("集合原数据:----------");
list.forEach(s -> System.out.println(s));
// list.removeIf(new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.contains("o");
// }
// });
// removeIf方法的功能:删除符合条件的元素
// 具体哪些元素符合条件取决于参数列表中Predcate接口中的test()的定义
list.removeIf(s -> s.contains("o"));
System.out.println("删除包含o字母的元素之后:----------");
list.forEach(s -> System.out.println(s));
}
}
用来根据一个类型的数据(T)得到另一个类型的数据®。
这类接口的抽象方法特点:既有参数又有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
Function<T,R> | R apply(T t) | 接收一个T类型对象,返回一个R类型对象结果 |
UnaryOperator | T apply(T t) | 接收一个T类型对象,返回一个T类型对象结果 |
DoubleFunction | R apply(double value) | 接收一个double值,返回一个R类型对象 |
IntFunction | R apply(int value) | 接收一个int值,返回一个R类型对象 |
LongFunction | R apply(long value) | 接收一个long值,返回一个R类型对象 |
ToDoubleFunction | double applyAsDouble(T value) | 接收一个T类型对象,返回一个double |
ToIntFunction | int applyAsInt(T value) | 接收一个T类型对象,返回一个int |
ToLongFunction | long applyAsLong(T value) | 接收一个T类型对象,返回一个long |
DoubleToIntFunction | int applyAsInt(double value) | 接收一个double值,返回一个int结果 |
DoubleToLongFunction | long applyAsLong(double value) | 接收一个double值,返回一个long结果 |
IntToDoubleFunction | double applyAsDouble(int value) | 接收一个int值,返回一个double结果 |
IntToLongFunction | long applyAsLong(int value) | 接收一个int值,返回一个long结果 |
LongToDoubleFunction | double applyAsDouble(long value) | 接收一个long值,返回一个double结果 |
LongToIntFunction | int applyAsInt(long value) | 接收一个long值,返回一个int结果 |
DoubleUnaryOperator | double applyAsDouble(double operand) | 接收一个double值,返回一个double |
IntUnaryOperator | int applyAsInt(int operand) | 接收一个int值,返回一个int结果 |
LongUnaryOperator | long applyAsLong(long operand) | 接收一个long值,返回一个long结果 |
BiFunction<T,U,R> | R apply(T t, U u) | 接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
BinaryOperator | T apply(T t, T u) | 接收两个T类型对象,返回一个T类型对象结果 |
ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | 接收一个T类型和一个U类型对象,返回一个double |
ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | 接收一个T类型和一个U类型对象,返回一个int |
ToLongBiFunction<T,U> | long applyAsLong(T t, U u) | 接收一个T类型和一个U类型对象,返回一个long |
DoubleBinaryOperator | double applyAsDouble(double left, double right) | 接收两个double值,返回一个double结果 |
IntBinaryOperator | int applyAsInt(int left, int right) | 接收两个int值,返回一个int结果 |
LongBinaryOperator | long applyAsLong(long left, long right) | 接收两个long值,返回一个long结果 |
功能型接口使用示例:
package com.atguigu.lambda;
import java.util.function.Function;
import java.util.function.UnaryOperator;
/**
* @author Sarah
* @description 体验函数式接口的四大类型
* 1.消费型接口 Comsumer 特点:有参数,无返回值
* void accept(T t) 接收一个对象用于完成功能
* 2.供给型接口 Supplier 特点:无参数,有返回值
* T get() 返回一个对象
* 3.判断型接口 Predcate 特点:有参数,返回值类型为boolean
* boolean test(T t) 接收一个对象
* 4. 功能型接口 Function/UnaryOperator 特点:有参数,有返回值
* R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
* 其中UnaryOperator接口的参数类型和返回值类型需一致
*/
public class FunntionInterfaceDemo {
public static void main(String[] args) {
Function<String,Integer> fun1 = s -> Integer.parseInt(s);
System.out.println(fun1.apply("22"));
Function<String,String> fun2 = s -> s.substring(0,1).toUpperCase();
System.out.println(fun2.apply("sarah"));
UnaryOperator<String> operator = s -> "尚硅谷";// 此接口要求参数类型和返回值类型保持一致
System.out.println(operator.apply("sarah"));
}
17.1.5 方法引用与构造器引用
17.1.5.1 方法引用
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式, 也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
jdk1.8提供了另外⼀种调⽤⽅式::,当你需要使⽤⽅法引⽤时,⽬标引⽤放在分隔符::前,⽅法的名称放在后⾯ ,即 ClassName :: methodName 。例如,Apple::getWeight 就是引⽤了Apple类中定义的⽅法getWeight。请记住,不需要括号,因为你没有实际调⽤这个⽅法。
Apple::getWeight⽅法引⽤就是"(Apple a) ->a.getWeight()"此Lambda表达式的快捷写法。
应用场景
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
当某个方法与函数式接口的抽象方法的方法签名一致时,可以使用方法引用简化Lambda表达式
方法标签一致,即函数式接口的参数列表和返回值类型,与方法引用的参数列表和返回值类型保持一致!
语法格式:
对象名::实例方法
类名::静态方法
类名::实例方法
格式说明:
- ::称为方法引用操作符(两个:中间不能有空格,而且必须英文状态下半角输入)
- Lambda表达式的形参列表,全部在Lambda体中使用上了,
- 类名.静态方法:Lambda表达式的形参列表与某类静态方法的形参列表完全一致
- 对象名.实例方法:Lambda表达式的形参列表与某类实例方法的形参列表完全一致
- 类名.实例方法:Lambda表达式的形参列表的第1个形参就是方法引用的对象,剩余的形参列表与所调用实例方法的形参列表一致。
- 在整个Lambda体中只有一条语句没有额外的数据。
举例
1> Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的
例如:System.out对象,调用println()方法来完成Lambda体
Math类,调用random()静态方法来完成Lambda体
2> 并且Lambda表达式的形参正好是给该方法的实参
例如:t->System.out.println(t)
() -> Math.random() 都是无参
代码演示
package com.atguigu.reference;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
public class MethodReference {
@Test
public void test4(){
String[] arr = {"Hello","java","chai"};
Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));
//用方法引用简化
/*
* Lambda表达式的形参,第一个(例如:s1),正好是调用方法的对象,剩下的形参(例如:s2)正好是给这个方法的实参
*/
Arrays.sort(arr, String::compareToIgnoreCase);
}
@Test
public void test3(){
Random random = new Random();
Supplier<Integer> s1 = () -> random.nextInt();
Supplier<Integer> s2 = random :: nextInt;//用方法引用简化
System.out.println(s1.get());
System.out.println(s2.get());
//上面两个写法是等价的
Supplier<Integer> s3 = () -> random.nextInt(100);
Supplier<Integer> s4 = random :: nextInt;//用方法引用简化 缺100
System.out.println(s3.get());
System.out.println(s4.get());
//上面两个写法是不等价的
}
@Test
public void test2(){
Supplier<Double> s1 = () -> Math.random();
Supplier<Double> s2 = Math :: random;//用方法引用简化
System.out.println(s1.get());
System.out.println(s2.get());
//上面两个写法是等价的
}
@Test
public void test1(){
List<Integer> list = Arrays.asList(1,3,4,8,9);
//list.forEach(t -> System.out.println(t));
//用方法引用再简化
list.forEach(System.out::println);
}
}
代码演示二:
package com.atguigu;
public class Demo1 {
/*
- 对象名.实例方法:Lambda表达式的形参列表与某类实例方法的形参列表完全一致
- 类名.静态方法:Lambda表达式的形参列表与某类静态方法的形参列表完全一致
- 类名.实例方法:Lambda表达式的形参列表的第1个形参就是调用方法的对象,
Lambda表达式的形参列表与所调用实例方法的形参列表一致。
*/
public static void main(String[] args) {
// - 对象名.实例方法:Lambda表达式的形参列表与某类实例方法的形参列表完全一致
ClassB cb = new ClassB();
cb.mt1(new InterAA() {
@Override
public void method1() {
System.out.println("--InterAA-----method1");
}
});
cb.mt1(() ->{ System.out.println("--InterAA-----method1"); });
cb.mt1(new ClassA()::show1);
// - 类名.静态方法:Lambda表达式的形参列表与某类静态方法的形参列表完全一致
System.out.println("=======================================");
cb.mt2(new InterBB() {
@Override
public String method2() {
return "------InterBB----method2";
}
});
cb.mt2(() ->"------InterBB----method2");
cb.mt2(ClassA::show2);
// 类名.实例方法:Lambda表达式的形参列表的第1个形参就是调用方法的对象,
// Lambda表达式的形参列表与所调用实例方法的形参列表一致。
System.out.println("=======================================");
cb.mt3(new InterCC() {
@Override
public void method3(ClassA aa, int num) {
aa.show3(num);
}
});
cb.mt3((aa,num) ->{aa.show3(num);});
cb.mt3(ClassA::show3);
}
}
class ClassB{
public void mt1(InterAA aa){
aa.method1();
}
public void mt2(InterBB bb){
System.out.println(bb.method2());
}
public void mt3(InterCC cc){
cc.method3(new ClassA(),88);
}
}
interface InterAA{
void method1();
}
interface InterBB{
String method2();
}
interface InterCC{
void method3(ClassA aa,int num);
}
class ClassA{
public void show1(){
System.out.println("---ClassA----show1----");
}
public static String show2(){
return "---ClassA----show2----";
}
public void show3(int num){
System.out.println("---ClassA----show3----" + num);
}
}
17.1.5.2 构造器引用
当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用:
- 类名::new
package com.atguigu;
import java.util.function.Function;
public class Demo1 {
/*
当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用:
- 类名::new
*/
public static void main(String[] args) {
// Function<String,Person> fun = s -> new Person(s);
Function<String,Person> fun2 = Person::new;
Person p = fun2.apply("张三");
System.out.println(p);
// Function<Integer,Student> fun3 = i -> new Student(i);
Function<Integer,Student> fun4 = Student::new;
System.out.println(fun4.apply(3));
}
}
class Person{
String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student{
int age;
public Student(int age) {
this.age = age;
}
@Override
public String toString() {
return "Studetn{" +
"age=" + age +
'}';
}
}
17.1.5.3 数组构造引用
当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用:
- 数组类型名::new
代码演示
package com.atguigu.reference;
import java.util.Arrays;
import java.util.function.Function;
public class ArrayCreateReference {
/*
当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用:
- 数组类型名::new
*/
public static void main(String[] args) {
// Function<Integer, Integer[]> function = (n) -> new Integer[n];
Function<Integer,Integer[]> function = Integer[]::new;
Integer[] arr = function.apply(5);
System.out.println(Arrays.toString(arr));
}
}
17.2 StreamAPI
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
17.2.1 Stream特点
Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”
注意:
1> Stream 自己不会存储元素。
2> Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。
3> Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
引入案例
案例需求
按照下面的要求完成集合的创建和遍历
- 创建一个集合,存储多个字符串元素
- 把集合中所有以"张"开头的元素存储到一个新的集合
- 把"张"开头的集合中的长度为3的元素存储到一个新的集合
- 遍历上一步得到的集合
代码演示:
package com.atguigu;
import java.util.ArrayList;
public class Demo1 {
/*
案例需求
按照下面的要求完成集合的创建和遍历
- 创建一个集合,存储多个字符串元素
- 把集合中所有以"张"开头的元素存储到一个新的集合
- 把"张"开头的集合中的长度为3的元素存储到一个新的集合
- 遍历上一步得到的集合
*/
public static void main(String[] args) {
//创建一个集合,存储多个字符串元素
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
// - 把集合中所有以"张"开头的元素存储到一个新的集合
ArrayList<String> list2 = new ArrayList<>();
for (String s : list) {
if(s.startsWith("张")){
list2.add(s);
}
}
System.out.println(list2);
// - 把"张"开头的集合中的长度为3的元素存储到一个新的集合
ArrayList<String> list3 = new ArrayList<>();
for (String s : list2) {
if(s.length() == 3){
list3.add(s);
}
}
// - 遍历上一步得到的集合
for (String s : list3) {
System.out.println(s);
}
System.out.println("-----------------");
// 使用Stream流完成
list.stream().filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
System.out.println("------------------");
// 方法引用
list.stream().filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
17.2.2 Stream流的思想特点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkagKEjF-1680573818946)(尚硅谷-JavaSE-第17章 Java8新特性-陈叶.assets/1663604798505.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdbgyjXh-1680573818947)(尚硅谷-JavaSE-第17章 Java8新特性-陈叶.assets/1663604863367.png)]
17.2.3 Stream 的操作三个步骤
-
获取Stream流
- 创建一条流水线,并把数据放到流水线上准备进行操作
-
中间方法
- 流水线上的操作
- 一次操作完毕之后,还可以继续进行其他操作
-
终结方法
- 一个Stream流只能有一个终结方法
- 是流水线上的最后一个操作
17.2.4 生成Stream流的方式
- Collection体系集合
使用默认方法stream()生成流, default Stream<E> stream()
- Map体系集合
把Map转成Set集合,**间接**的生成流
- 数组
通过Arrays中的静态方法stream生成流
- 同种数据类型的多个数据
通过Stream接口的静态方法of(T... values)生成流
如:1,2,3,4,5,...
" aa","bb","cc"," dd"...
代码演示:
package com.atguigu;
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Demo1 {
/*
- Collection体系集合
使用默认方法stream()生成流, default Stream<E> stream()
- Map体系集合
把Map转成Set集合,**间接**的生成流
- 数组
通过Arrays中的静态方法stream生成流
- 同种数据类型的多个数据
通过Stream接口的静态方法of(T... values)生成流
如:1,2,3,4,5,...
" aa","bb","cc"," dd"...
*/
public static void main(String[] args) {
// - Collection体系集合
// 使用默认方法stream()生成流, default Stream<E> stream()
Stream<Object> stream1 = new ArrayList<>().stream();
Stream<Object> stream2 = new HashSet<>().stream();
// - Map体系集合
// 把Map转成Set集合,**间接**的生成流
Stream<Integer> stream3 = new HashMap<Integer, String>().keySet().stream();
Stream<Map.Entry<Integer, String>> stream4 = new HashMap<Integer, String>().entrySet().stream();
// - 数组
// 通过Arrays中的静态方法stream生成流
Stream<String> stream5 = Arrays.stream(new String[5]);
// - 同种数据类型的多个数据
// 通过Stream接口的静态方法of(T... values)生成流
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4);
Stream<String> stream7 = Stream.of("aa", "bb");
}
}
17.2.5 中间操作API
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
执行完此方法之后,Stream流依然可以继续执行其他操作
常见方法
方法名 | 说明 |
---|---|
Stream filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Stream limit(long maxSize) | 返回此流中的元素组成的流,截取前指定参数个数的数据 |
Stream skip(long n) | 跳过指定参数个数的数据,返回由该流的剩余元素组成的流 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream distinct() | 返回由该流的不同元素(根据Object.equals(Object) )组成的流 |
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
需求:
1> 把list集合中以张开头的,长度为3的元素在控制台输出
2> 取前3个数据在控制台输出
3> 跳过4个元素,把剩下的元素在控制台输出
4> 跳过2个元素,把剩下的元素中前2个在控制台输出
5> 取前4个数据组成一个流
6> 跳过2个数据组成一个流
7> 合并需求5和需求6得到的流,并把结果在控制台输出
8> 合并需求5和需求6得到的流,并把结果在控制台输出,要求字符串元素不能重复
代码演示
package com.atguigu;
import org.junit.Test;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo1 {
// 把list集合中以张开头的,长度为3的元素在控制台输出
@Test
public void test1(){
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
// list.stream().filter(new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.startsWith("张");
// }
// });
// list.stream().filter(s ->s.startsWith("张"));
list.stream()
.filter(s->s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s-> System.out.println(s));
}
/*
limit&skip
2> 取前3个数据在控制台输出
3> 跳过4个元素,把剩下的元素在控制台输出
4> 跳过2个元素,把剩下的元素中前2个在控制台输出
*/
@Test
public void test2(){
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
// 2> 取前3个数据在控制台输出
list.stream().limit(3).forEach(System.out::println);
System.out.println("---------------");
// 3> 跳过4个元素,把剩下的元素在控制台输出
list.stream().skip(4).forEach(System.out::println);
System.out.println("---------------");
// 4> 跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(System.out::println);
}
/*
5> 取前4个数据组成一个流
6> 跳过2个数据组成一个流
7> 合并需求5和需求6得到的流,并把结果在控制台输出
8> 合并需求5和需求6得到的流,并把结果在控制台输出,要求字符串元素不能重复
*/
@Test
public void test3(){
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
// 5> 取前4个数据组成一个流
Stream<String> stream1 = list.stream().limit(4);
// 6> 跳过2个数据组成一个流
Stream<String> stream2 = list.stream().skip(2);
// 7> 合并需求5和需求6得到的流,并把结果在控制台输出
// Stream.concat(stream1,stream2).forEach(System.out::println);
// 8> 合并需求5和需求6得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(stream1,stream2).distinct().forEach(System.out::println);
}
}
17.2.6 终结操作API
终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
常见方法
方法名 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行操作 |
long count() | 返回此流中的元素数 |
需求:
1> 把集合中的元素在控制台输出
2> 统计集合中有几个以张开头的元素,并把统计结果在控制台输出
代码演示
package com.atguigu;
import org.junit.Test;
import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class Demo1 {
// 终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
@Test
public void test1(){
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
// list.stream().forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
// list.stream().forEach(s -> System.out.println(s));
list.stream().forEach(System.out::println);
long count = list.stream().filter(s -> s.startsWith("张")).count();
System.out.println("count = " + count);
}
}
oncat(stream1,stream2).distinct().forEach(System.out::println);
}
}
### 17.2.6 终结操作API
```tex
终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
常见方法
方法名 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行操作 |
long count() | 返回此流中的元素数 |
需求:
1> 把集合中的元素在控制台输出
2> 统计集合中有几个以张开头的元素,并把统计结果在控制台输出
代码演示
package com.atguigu;
import org.junit.Test;
import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class Demo1 {
// 终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
@Test
public void test1(){
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");
// list.stream().forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// });
// list.stream().forEach(s -> System.out.println(s));
list.stream().forEach(System.out::println);
long count = list.stream().filter(s -> s.startsWith("张")).count();
System.out.println("count = " + count);
}
}