转载自 https://www.jianshu.com/p/613a6118e2e0 (java8 lambda 表达式详解)
函数式接口
函数式接口是仅含一个抽象方法的接口,但可以指定 Object 定义的任何公有方法。
- 这是一个函数式接口
@FunctionalInterface
public interface IFuntionSum<T extends Number> {
T sum(List<T> numbers); // 抽象方法
}
- 这也是个函数式接口
@FunctionalInterface
public interface IFunctionMulti<T extends Number> {
void multi(List<T> numbers); // 抽象方法
boolean equals(Object obj); // Object中的方法
}
其中 equals() 是java中Object中自带实现的方法,所以并不是抽象方法,以上接口依旧只有一个抽象方法。
- 下面则不是函数接口
@FunctionalInterface
public interface IFunctionMulti1<T extends Number> extends IFuntionSum<T> {
void multi(List<T> numbers);
}
@FunctionalInterface
public interface IFunctionMulti2<T extends Number> {
void multi(List<T> numbers);
T sum(List<T> numbers);
}
以上两个接口都有两个抽象方法,所以并不是函数式接口。
tip 1: 可以用 @FunctionalInterface 标识函数式接口,非强制要求,但有助于编译器及时检查接口是否满足函数式接口定义
tip 2: 在 Java 8 之前,接口的所有方法都是抽象方法,在 Java 8 中新增了接口的默认方法
lambda表达式
- lambda 表达式的2种形式
包含单独表达式 :parameters -> an expression
list.forEach(item -> System.out.println(item));
包含代码块:parameters -> { expressions };
list.forEach(item -> {
int numA = item.getNumA();
int numB = item.getNumB();
System.out.println(numA + numB);
});
每个 lambda 表达式背后必定有一个函数式接口,该表达式实现的是这个函数式接口内部的唯一抽象方法。
举个例子:
这是一个lambda表达式的实现
public class LambdaDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("item1", "item2");
list.forEach(item -> System.out.println(item));
}
}
其中item -> System.out.println(item)是lambda表达式,那其函数式接口呢?
源码大致如下:
@Override
public void forEach(Consumer<? super E> action) {
// ......
}
由上可以看出其函数式接口是Consumer这个接口。
那这个接口的实现呢?如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t); // lambda 表达式 item -> System.out.println(item) 实现了该方法
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
其中的抽象函数就是accept(),item -> System.out.println(item)就是实现了accept()这个方法。
那不妨在多了解一点。
就是上文中LambdaDemo()这个方法生成的实例类名为 LambdaDemo$$Lambda$1。
- 匿名内部类 和 lambda 表达式匿名内部类的命名规则
内部类的命名规则:外部类名 + $ + 内部类名
匿名类的命名规则:外部类名 + $ + (1, 2, 3,第几个匿名类就显示几)
lambada 匿名内部类的命名规则:外部类名 + $$ + Lambda + $ + (1, 2, 3,第几个lambda表达式就显示几)
根据匿名内部类的命名规则可知,这是 LamddaDemo的 一个匿名内部类。
由于其是一个类的原因,lambda 表达式在执行时会生成目标函数式接口的类实例。
因此有以下操作
// 有以下函数式接口
@FunctionalInterface
public interface IFuntionSum<T extends Number> {
T sum(List<T> numbers);
}
// 将一个lambda表达式赋值给函数式接口引用(类型须兼容)
IFuntionSum<Long> function = list -> {
Long sum = 0L;
for (Long item : list) {
sum += item;
}
return sum;
};
function.sum(Arrays.asList(1L, 2L)); // 执行结果为3L
在开发过程中,我们可以将 lambda 表达式等同于一个对象使用,对其声明、引用、传递。
lambda表达式规约
- lambda 表达式的参数可以通过上下文推断,如果需要显示声明一个参数的类型,则必须为所有的参数声明类型。
@FunctionalInterface
public interface IFunctionMod {
boolean (int n, int d);
}
IFunctionMod function = (n, d) -> (n % d) == 0 // 合理,n 和 d 的类型通过上下文推断
IFunctionMod function = (int n, int d) -> (n % d) == 0 // 合理,指定 n 和 d 的类型
IFunctionMod function = (int n, d) -> (n % d) == 0 // 不合理,须显示声明所有参数类型
- lambda 表达式中抛出的异常需要与目标函数式接口的抽象方法抛出的异常类型兼容:
以下是合理的:
@FunctionalInterface
public interface IFunctionMod {
boolean (int n, int d) throw Exception;
}
IFunctionMod function = (n, d) -> {
if (d == 0) {
// IOException是EXception 的子类,通过类型转换,IOException 可转换为 Exception
throw new IOException("test");
}
return n % d == 0;
};
反之,不行
@FunctionalInterface
public interface IFunctionMod {
boolean (int n, int d) throw IOException;
}
IFunctionMod function = (n, d) -> {
if (d == 0) {
// 父类不能通过自动类型转换转为子类,lambda 表达式抛出的异常类型与抽象方法抛出的异常类型不兼容
throw new Exception("test");
}
return n % d == 0;
};
- lambda 表达式中参数类型需要与目标函数式接口中抽象方法的参数类型兼容。
方法引用
list.forEach(System.out::print)
以上是通过系统中System.out这个对象中的out这个已有方法直接构造lambda表达式。语法就如上。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业