持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情
函数式编程思想
-
面向对象的思想:
-
- 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
-
函数式编程思想:
-
- 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
- Java8 引入了 Lambda 表达式之后,Java也开始支持函数式编程
- 可以这么说 Lambda 表达式其实就是实现 SAM 接口的语法糖,使得 Java 也算是支持函数式编程的语言 Lambda
备注:“语法糖”是指使用更加方便,但是原理不变的代码语法例如在遍历集合时使用的 for-each 语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”从应用层面来讲,Java 中的 Lambda 可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的
冗余的匿名内部类
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程
public class Demo01Runnable {
public static void main(String[] args) {
// 匿名内部类
Runnable task = new Runnable() {
@Override
public void run() { // 覆盖重写抽象方法
System.out.println("多线程任务执行!");
}
};
new Thread(task).start(); // 启动线程
}
}
复制代码
本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable
接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动
代码分析:
对于Runnable
的匿名内部类用法,可以分析出几点内容:
Thread
类需要Runnable
接口作为参数,其中的抽象run
方法是用来指定线程任务内容的核心- 为了指定
run
的方法体,不得不需要Runnable
接口的实现类 - 为了省去定义一个
RunnableImpl
实现类的麻烦,不得不使用匿名内部类 - 必须覆盖重写抽象
run
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错 - 而实际上,似乎只有方法体才是关键所在
编程思想转换
做什么,而不是谁来做,怎么做
真的希望创建一个匿名内部类对象吗?不,只是为了做这件事情而不得不创建一个对象,真正希望做的事情是:将run
方法体内的代码传递给Thread
类
传递一段代码
这才是真正的目的,而创建对象只是受限于面向对象语法而不得不采取的一种手段方式
那有没有更加简单的办法?如果将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要
生活举例
- 当需要从北京到上海【这是目的】
- 可以选择高铁、汽车、骑行或是徒步的【这是形式】
- 真正目的是到达上海,而如何才能到达上海的形式并不重要,所以一直在探索有没有比高铁更好的方式——搭乘飞机
体验 Lambda 的更优写法
使用 Lambda 语法,上述Runnable
接口的匿名内部类写法可以通过更简单的 Lambda 表达式达到等效
public class Demo02LambdaRunnable {
public static void main(String[] args) {
new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
}
}
复制代码
这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通
从代码的语义中可以看出:启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定
函数式接口
SAM 接口
- Single Abstract Method
- Lambda 表达式其实就是实现SAM接口的语法糖
- 该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法
@FunctionalInterface
- 其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用 Lambda 表达式
- 最好在声明接口时,加上
@FunctionalInterface
- 一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错
标记了@FunctionalInterface
的函数式接口的有:Runnable,Comparator,FileFilter
Java8 在 java.util.function 新增了很多函数式接口,主要分为四大类:消费型、供给型、判断型、功能型
基本可以满足的开发需求
自定义函数式接口
只要确保接口中有且仅有一个抽象方法即可
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
复制代码
接口当中抽象方法的 public abstract 是可以省略的
声明一个计算器Calculator
接口,内含抽象方法calc
可以对两个 int数字进行计算,并返回结果
public interface Calculator {
int calc(int a, int b);
}
复制代码
在测试类中,声明一个如下方法
public static void invokeCalc(int a, int b, Calculator calculator) {
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
复制代码
下面进行测试
使用匿名内部类的方式
public static void main(String[] args) {
invokeCalc(1, 2, new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
});
invokeCalc(1, 2, new Calculator() {
@Override
public int calc(int a, int b) {
return a * b;
}
});
}
复制代码
使用 lambda 的方式
public static void main(String[] args) {
invokeCalc(1, 2, Integer::sum);
invokeCalc(1, 2, (a, b) -> a - b);
invokeCalc(1, 2, (a, b) -> a * b);
invokeCalc(1, 2, (a, b) -> a / b);
invokeCalc(1, 2, (a, b) -> a % b);
}
复制代码
消费型接口
消费型接口的抽象方法特点:有形参,但是返回值类型是 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值 |
供给型接口
这类接口的抽象方法特点:无参
接口名 | 抽象方法 | 描述 |
---|---|---|
Supplier | T get() | 返回一个对象 |
BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
DoubleSupplier | double getAsDouble() | 返回一个double值 |
IntSupplier | int getAsInt() | 返回一个int值 |
LongSupplier | long getAsLong() | 返回一个long值 |
判断型接口
这里接口的抽象方法特点:有参,但是返回值类型是 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值 |
\
功能型接口
这类接口的抽象方法特点:既有参数又有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
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结果 |
\