Java - lambda 函数式接口

持续创作,加速成长!这是我参与「掘金日新计划 · 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

接口名抽象方法描述
Consumervoid accept(T t)接收一个对象用于完成功能
BiConsumer<T,U>void accept(T t, U u)接收两个对象用于完成功能
DoubleConsumervoid accept(double value)接收一个double值
IntConsumervoid accept(int value)接收一个int值
LongConsumervoid accept(long value)接收一个long值
ObjDoubleConsumervoid accept(T t, double value)接收一个对象和一个double值
ObjIntConsumervoid accept(T t, int value)接收一个对象和一个int值
ObjLongConsumervoid accept(T t, long value)接收一个对象和一个long值

供给型接口

这类接口的抽象方法特点:无参

接口名抽象方法描述
SupplierT get()返回一个对象
BooleanSupplierboolean getAsBoolean()返回一个boolean值
DoubleSupplierdouble getAsDouble()返回一个double值
IntSupplierint getAsInt()返回一个int值
LongSupplierlong getAsLong()返回一个long值

判断型接口

这里接口的抽象方法特点:有参,但是返回值类型是 boolean 结果

接口名抽象方法描述
Predicateboolean test(T t)接收一个对象
BiPredicate<T,U>boolean test(T t, U u)接收两个对象
DoublePredicateboolean test(double value)接收一个double值
IntPredicateboolean test(int value)接收一个int值
LongPredicateboolean test(long value)接收一个long值

\

功能型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名抽象方法描述
Function<T,R>R apply(T t)接收一个T类型对象,返回一个R类型对象结果
UnaryOperatorT apply(T t)接收一个T类型对象,返回一个T类型对象结果
DoubleFunctionR apply(double value)接收一个double值,返回一个R类型对象
IntFunctionR apply(int value)接收一个int值,返回一个R类型对象
LongFunctionR apply(long value)接收一个long值,返回一个R类型对象
ToDoubleFunctiondouble applyAsDouble(T value)接收一个T类型对象,返回一个double
ToIntFunctionint applyAsInt(T value)接收一个T类型对象,返回一个int
ToLongFunctionlong applyAsLong(T value)接收一个T类型对象,返回一个long
DoubleToIntFunctionint applyAsInt(double value)接收一个double值,返回一个int结果
DoubleToLongFunctionlong applyAsLong(double value)接收一个double值,返回一个long结果
IntToDoubleFunctiondouble applyAsDouble(int value)接收一个int值,返回一个double结果
IntToLongFunctionlong applyAsLong(int value)接收一个int值,返回一个long结果
LongToDoubleFunctiondouble applyAsDouble(long value)接收一个long值,返回一个double结果
LongToIntFunctionint applyAsInt(long value)接收一个long值,返回一个int结果
DoubleUnaryOperatordouble applyAsDouble(double operand)接收一个double值,返回一个double
IntUnaryOperatorint applyAsInt(int operand)接收一个int值,返回一个int结果
LongUnaryOperatorlong applyAsLong(long operand)接收一个long值,返回一个long结果
BiFunction<T,U,R>R apply(T t, U u)接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperatorT 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
DoubleBinaryOperatordouble applyAsDouble(double left, double right)接收两个double值,返回一个double结果
IntBinaryOperatorint applyAsInt(int left, int right)接收两个int值,返回一个int结果
LongBinaryOperatorlong applyAsLong(long left, long right)接收两个long值,返回一个long结果

\

分类:
后端