Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。Lambda表达式是jdk1.8才有的一种新特性。
为什么会有Lambda表达式
我们写了这样一个计算器类,类中现在有一个加法和一个减法。
public class Calculator {
public static void main(String[] args) {
//参与运算的第一个数
int a = Integer.parseInt(args[0]);
//参与运算的第二个数
int b = Integer.parseInt(args[1]);
//参与运算的方法
String method = args[2];
Calculator calculator = new Calculator();
if ("add".equals(method)) {
System.out.println(calculator.add(a, b));
} else if ("sub".equals(method)) {
System.out.println(calculator.sub(a, b));
} else {
System.out.println("没有找到计算方法");
}
}
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
}
假如入我们要实现一个乘法和除法,那我们必须又要增加两个方法,一个乘法和一个除法
假如后面我们又有其他的要求,那是不是得又增加许多方法,那有没有一种比较简单且通用的方法来实现我们需要的功能呢
我们先观察一下Calculator方法中的方法,add,sub以及我们将要实现的乘法和除法,都是接收两个参数,返回一个值 ,因此我们可以考虑封装一个算法接口,将算法封装起来
假如我们实现了一个这样的算法接口
public interface ComputeStrategy {
int doCompute(int p1,int p2);
}
然后实现一个加法计算和一个减法计算策略
public class AddComputeStrategy implements ComputeStrategy {
@Override
public int doCompute(int p1, int p2) {
return p1 + p2;
}
}
public class SubComputeStrategy implements ComputeStrategy {
@Override
public int doCompute(int p1, int p2) {
return p1 - p2;
}
}
然后将Calculator类改造一下,引入我们的接口
public class Calculator {
private ComputeStrategy computeStrategy;
public static void main(String[] args) {
//参与运算的第一个数
int a = Integer.parseInt(args[0]);
//参与运算的第二个数
int b = Integer.parseInt(args[1]);
//参与运算的方法
String method = args[2];
Calculator calculator = new Calculator();
if ("add".equals(method)) {
//设置加法策略
ComputeStrategy computeStrategy = new AddComputeStrategy();
calculator.setComputeStrategy(computeStrategy);
int result = calculator.doCalculate(a, b);
System.out.println(result);
} else if ("sub".equals(method)) {
//设置减法策略
ComputeStrategy computeStrategy = new SubComputeStrategy();
calculator.setComputeStrategy(computeStrategy);
int result = calculator.doCalculate(a, b);
System.out.println(result);
} else {
System.out.println("没有找到计算方法");
}
}
public void setComputeStrategy(ComputeStrategy computeStrategy) {
this.computeStrategy = computeStrategy;
}
public int doCalculate(int p1, int p2) {
return computeStrategy.doCompute(p1, p2);
}
}
这样以后我们要增加其他算法,也就只需要增加实现ComputeStrategy接口的子类就可以了,但是这样如果算法很多,并且复用性又比较低怎么办,不可能为每个策略都去写一个实现类,有可能它就只被用到一次,这样做的意义并不大,这个时候我们可以用匿名实现类来代替,比如我们实现一个两个数相加,再翻倍的算法。
public class Calculator {
private ComputeStrategy computeStrategy;
public static void main(String[] args) {
//参与运算的第一个数
int a = Integer.parseInt(args[0]);
//参与运算的第二个数
int b = Integer.parseInt(args[1]);
//参与运算的方法
String method = args[2];
Calculator calculator = new Calculator();
if ("add".equals(method)) {
//设置加法策略
ComputeStrategy computeStrategy = new AddComputeStrategy();
calculator.setComputeStrategy(computeStrategy);
int result = calculator.doCalculate(a, b);
System.out.println(result);
} else if ("sub".equals(method)) {
//设置减法策略
ComputeStrategy computeStrategy = new SubComputeStrategy();
calculator.setComputeStrategy(computeStrategy);
int result = calculator.doCalculate(a, b);
System.out.println(result);
} else if ("addAndDouble".equals(method)) {
//两数相加再翻倍
calculator.setComputeStrategy(new ComputeStrategy() {
@Override
public int doCompute(int p1, int p2) {
return (p1 + p2) * 2;
}
});
int result = calculator.doCalculate(a, b);
System.out.println(result);
} else {
System.out.println("没有找到计算方法");
}
}
public void setComputeStrategy(ComputeStrategy computeStrategy) {
this.computeStrategy = computeStrategy;
}
public int doCalculate(int p1, int p2) {
return computeStrategy.doCompute(p1, p2);
}
}
重点来看这段代码
calculator.setComputeStrategy(new ComputeStrategy() {
@Override
public int doCompute(int p1, int p2) {
return (p1 + p2) * 2;
}
});
这是Java匿名类的写法,这个类实现了ComputeStrategy接口,实现的算法是两数相加并且把结果翻倍。
那有没有一种简单的方法来表示呢,答案是有的,可以用JDK8新增的Lambda表达式来代替
calculator.setComputeStrategy((p1, p2) -> (p1 + p2) * 2);
可以看到,只需要一行代码就可以代替上面的整个匿名类。
虽然上述Lambda表达式看起来只是将语法做了精简,实际上也是一种思想上的转变,它其实是函数作为了方法的入参,这和面向对象的思想还是有区别的。
Lambda表达式的局限性
局限性一
首先接口必须有且只能有一个抽象方法需要子类去实现的,接口的default修饰的方法,由于不需要子类强制去实现,因此也不算在内
只有这样编译器才能正确的推断出调用的哪个方法以及参数的信息,并且Lambda表达式目前支持接口,不支持抽象类
比如这样一个接口,就无法用Lambda表达式来代替
public interface A {
void doXX1();
void doXX2();
}
因为Lambda表达式只能表示一个函数,而A接口两个方法都是需要实现的,如果用Lambda表示无法知道使用哪一个,因此这种情况下是使用不了Lambda表达式来代替匿名实现类的。
从这里也能得到另外一个知识,那就是只要满足这个条件,就可以用Lambda表达式表示
比如我们之前经常写的Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};
这种我们就可以用Lambda表达式来表示,该方法没有参数没有返回值,因此我们可以写成
Runnable runnable = () -> System.out.println("run");
()里没有参数表示参数为空
->为Lambda表达式分隔符
System.out.println(“run”)为方法体
局限性二
在Lambda表达式中使用的变量必须为不可变或者是一个既成事实上的 final 变量 ,比如下面这段代码,虽然num没有用final修饰,但是也没有二次赋值,因此可以认为它是final的,也就能在Lambda表达式中使用
int num = 2; calculator.setComputeStrategy((p1, p2) -> (p1 + p2) * num);
但是如果我们再对其赋值一次,如下面这段代码,那么就会编译不通过,因为num二次赋值意味着不再是final的。
int num = 2; num = 3; calculator.setComputeStrategy((p1, p2) -> (p1 + p2) * num);



