Java函数式编程 - Lambda表达式
1.概述
1.1 为什么学?
- 看得懂公司代码
- 大数据量下高效率处理集合
- 提高代码可读性
- 消灭嵌套的垃圾代码
如果你现在写代码还在 ,不停的 if else for 那一定会在代码走查的时候被项目经理吊起来锤,那么学习了函数式,所有问题迎刃而解
1.2 什么是Lambda表达式
- lambada 表达式实质上是一个匿名方法,但该方法并非独立执行,而是用于实现由函数式接口定义的唯一抽象方法
- 使用 lambda 表达式时,会创建实现了函数式接口的一个匿名类实例
- 可以将 lambda 表达式视为一个对象,可以将其作为参数传递
1.3 核心原则
可推导可省略: 不关注类名,方法名。只关注方法体!
1.4 基本格式
(参数类型 参数名称) ->{
方法体;
}
2.使用
2.1 例1:
创建线程并启动可以使用匿名内部类也可以使用Lambda表达式
//使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类启动线程");
}
}).start();
//使用Lambda表达式
new Thread(() -> System.out.println("匿名内部类启动线程")).start();
备注: 这里可以有个小技巧,在idea中鼠标光标移动到Runnable这个位置 alt+回车 可以直接转化成lambda的写法,非常方便。从上面我们可以看出,lambda表达式写法更精简,代码看起来更整洁。
2.1 例2:
public static void main(String[] args) {
//输出偶数
v2((i)-> i%2 ==0);
}
public static void v2(IntPredicate intPredicate){
int[] arr ={1,2,3,4,5,6,7,8,9,10};
for(int i:arr){
if(intPredicate.test(i)){
System.out.println(i);
}
}
}
提示: 如果一下子写不出来lambda的可以先写匿名内部类,在转化成lambda表达式的形式,参照例1
2.1 例3:
ArrayList 中 foreach 方法源代码:
public void forEach(Consumer<? super E> var1) {
Objects.requireNonNull(var1);
int var2 = this.modCount;
Object[] var3 = (Object[])this.elementData;
int var4 = this.size;
for(int var5 = 0; this.modCount == var2 && var5 < var4; ++var5) {
var1.accept(var3[var5]);
}
if (this.modCount != var2) {
throw new ConcurrentModificationException();
}
}
其中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); };
}
}
既然foreach这个方法的参数是一个函数式接口了,那么我们使用 foreach的时候我们就可以用lambda啦。
我们可以这样去遍历集合:
public static void main(String[] args) {
List<String> list =new ArrayList<>();
list.add("亚索");
list.add("盖伦");
list.add("提莫");
list.forEach(s -> System.out.println(s));
}
打印执行结果:
这样看来比用foreach写法就简单多了。
用 Java VisualVM 追踪代码运行过程中的堆内存,发现会生成以下实例:

由此,我们可知 lambda 表达式在执行时会生成目标函数式接口的类实例,因此我们可以做以下操作,可以将 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 表达式匿名内部类的命名规则
内部类的命名规则:外部类名 + $ + 内部类名
匿名类的命名规则:外部类名 + $ + (1, 2, 3,第几个匿名类就显示几)
lambada 匿名内部类的命名规则:外部类名 + $$ + Lambda + $ + (1, 2, 3,第几个lambda表达式就显示几)
3.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;
};
说明: 这一点也比较好理解,其实lamdba可以理解成,函数接口的实现类那就不能抛出比父类更多的异常!
4.lambda表达式省略规则
- 参数类型可以直接省略
- 方法体内,若只有一句代码,那么大括号,
分号,return都是可以省略 - 方法只有一个参数的时候,小括号可以省略
5.总结
5.1 使用场景
Lambda表达式语法是非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
- 方法的参数或局部变量类型必须是接口才能使用Lambda
- 接口中有且仅有一个抽象方法(@FunctionalIneterface)
5.2 与匿名内部类的对比
- 所需类型不同: 匿名内部类的类型可以是普通类,接口,抽象类,lambda表达式所需类型必须是接口
- 抽象方法的个数不同: 匿名内部类所需的接口中的抽象方法的数量是随意的。Lambda表达式所需的接口中只能有一个抽象方法
- 实现原理不同: 匿名内部类是在编译后形成.class而Lambda表达式是在程序运行的时候动态生成.class