https://blog.csdn.net/qfchenjunbo/article/details/121902206?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163955251016780366580258%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=163955251016780366580258&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-8-121902206.pc_search_insert_es_download_v2&utm_term=java1.8%E4%B9%8Bsupplier&spm=1018.2226.3001.4187
编程中的函数,调用的时候,给定实参为形参赋值,然后通过运行方法体,返回一个结果。对于调用者来做,关注这个方法具备什么样的功能。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
函数式编程思想:只要能获取到结果,谁去做的不重要,重视的是结果,不重视过程。
Java8 引入了 Lambda 表达式之后,Java 也开始支持函数式编程。
lambda 表达式其实就是实现 SAM 接口的语法糖,使得 Java 也算是支持函数式编程的语言。
**备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。**例如在遍历集合时使用的 for-each 语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java 中的 Lambda 可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。
当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable 接口来定义任务内容,并使用 java.lang.Thread 类来启动该线程。代码如下:
public class Demo01 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {// 覆盖重写抽象方法
System.out.println("使用匿名内部类重写的 run 方法");
}
};
new Thread(runnable).start(); // 启动线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类不创建对象重写的 run 方法");
}
}).start();
}
}
本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
代码分析:
对于 Runnable 的匿名内部类用法,可以分析出几点内容:
做什么,而不是谁来做,怎么做
我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将run方法体内的代码传递给Thread类知晓。
传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。
生活举例:
当我们需要从北京到上海时,可以选择高铁、汽车、骑行或是徒步。我们的真正目的是到达上海,而如何才能到达上海的形式并不重要,所以我们一直在探索有没有比高铁更好的方式——搭乘飞机。
而现在这种飞机(甚至是飞船)已经诞生:2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。
借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:
这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!
lambda表达式其实就是实现SAM接口的语句,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。
其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
之前的SAM接口中,标记了@FunctionalInterface的函数式接口的有:Runnable,Comparator,FileFilter。
Java8在java.util.function新增了很多函数式接口:主要分为四大类,消费型、供给型、判断型、功能型。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口。
只要确保接口中有且仅有一个抽象方法即可:
接口当中抽象方法的 public abstract 是可以省略的
例如:声明一个计算器Calculator接口,内含抽象方法calc可以对两个int数字进行计算,并返回结果:
在测试类中,声明一个如下方法:
下面进行测试:
除了我们可以自定义函数式接口之外,jdk也给我们内置了一些函数式接口,具体分类如下
2.2.1 消费型接口
消费型接口的抽象方法特点:有形参,但是返回值类型是void
接口名 抽象方法 描述
Consumer void accept(T t) 接收一个对象用于完成功能
BiConsumer
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值
2.2.2 供给型接口
这类接口的抽象方法特点:无参,但是有返回值
接口名 抽象方法 描述
Supplier T get() 返回一个对象
BooleanSupplier boolean getAsBoolean() 返回一个boolean值
DoubleSupplier double getAsDouble() 返回一个double值
IntSupplier int getAsInt() 返回一个int值
LongSupplier long getAsLong() 返回一个long值
2.2.3 断言型接口
这里接口的抽象方法特点:有参,但是返回值类型是boolean结果。
接口名 抽象方法 描述
Predicate boolean test(T t) 接收一个对象
BiPredicate
DoublePredicate boolean test(double value) 接收一个double值
IntPredicate boolean test(int value) 接收一个int值
LongPredicate boolean test(long value) 接收一个long值
2.2.4 功能型接口
这类接口的抽象方法特点:既有参数又有返回值
接口名 抽象方法 描述
Function
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
BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction
ToIntBiFunction
ToLongBiFunction
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结果
Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。
其实本质上,Lambda表达式是用于实现【函数式接口】的“抽象方法”
Lambda表达式语法格式
说明:
优化:Lambda表达式可以精简
示例代码:
ps:以下演示使用了Junit进行单元测试,不明白的可以先学习一下Junit
扫VX 领Java资料,前端,测试,python等等资料都有
4.1.1 自定义消费型接口测试
定义接口:
接口测试:
4.1.2 jdk中消费型接口测试
代码示例:Consumer接口
在JDK1.8中Collection集合接口的父接口Iterable接口中增加了一个默认方法:
public default void forEach(Consumer super T> action)遍历Collection集合的每个元素,执行“xxx消费型”操作。
在JDK1.8中Map集合接口中增加了一个默认方法:
public default void forEach(BiConsumer super K,? super V> action)遍历Map集合的每对映射关系,执行“xxx消费型”操作。
案例:
(1)创建一个Collection系列的集合,添加你知道的编程语言,调用forEach方法遍历查看
(2)创建一个Map系列的集合,添加一些(key,value)键值对,例如,添加编程语言排名和语言名称,调用forEach方法遍历查看
示例代码:
4.2.1 自定义供给型接口测试
定义接口:
接口测试:
4.2.2 jdk中供给型接口测试
代码示例:Supplier接口
在JDK1.8中增加了StreamAPI,java.util.stream.Stream是一个数据流。这个类型有一个静态方法:
public static Stream generate(Supplier s)可以创建Stream的对象。而又包含一个forEach方法可以遍历流中的元素:public void forEach(Consumer super T> action)。
案例:
现在请调用Stream的generate方法,来产生一个流对象,并调用Math.random()方法来产生数据,为Supplier函数式接口的形参赋值。最后调用forEach方法遍历流中的数据查看结果。
4.3.1 自定义功能型接口测试
定义接口:
接口测试:
4.3.2 jdk中功能型接口测试
代码示例:Funtion
在JDK1.8时Map接口增加了很多方法,例如:
public default void replaceAll(BiFunction super K,? super V,? extends V> function)按照function指定的操作替换map中的value。
public default void forEach(BiConsumer super K,? super V> action)遍历Map集合的每对映射关系,执行“xxx消费型”操作。
4.4.1 自定义断言型接口测试
定义接口:
接口测试:
4.4.2 jdk中断言型接口测试
代码示例:Predicate接口
JDK1.8时,Collecton接口增加了一下方法,其中一个如下:
public default boolean removeIf(Predicate super E> filter) 用于删除集合中满足filter指定的条件判断的。
public default void forEach(Consumer super T> action)遍历Collection集合的每个元素,执行“xxx消费型”操作。
(1)声明一个Employee员工类型,包含编号、姓名、性别,年龄,薪资。
(2)声明一个EmployeeSerice员工管理类,包含一个ArrayList集合的属性all,在EmployeeSerice的构造器中,创建一些员工对象,为all集合初始化。
(3)在EmployeeSerice员工管理类中,声明一个方法:ArrayList get(Predicate p),即将满足p指定的条件的员工,添加到一个新的ArrayList 集合中返回。
(4)在测试类中创建EmployeeSerice员工管理类的对象,并调用get方法,分别获取:
示例代码:
Employee类:
员工管理类:
测试类:
Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。当Lambda表达式满足一些特殊的情况时,还可以再简化:
(1)Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的
例如:System.out对象,调用println()方法来完成Lambda体
(2)并且Lambda表达式的形参正好是给该方法的实参
例如:t->System.out.println(t)
方法引用的语法格式:
(1)实例对象名::实例方法
(2)类名::静态方法
(3)类名::实例方法
说明:
(1)当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表。
(2) 当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度
构造器引用的语法格式:
示例代码:
扫VX 领Java资料,前端,测试,python等等资料都有


![[Java] 函数式编程与 Lambda 表达式 [Java] 函数式编程与 Lambda 表达式](http://www.mshxw.com/aiimages/31/666131.png)
