理解lambda表达式之前,需要先理解行为参数化和函数式编程的概念。
1、行为参数化观察如下的代码:
(1)根据用户输入的两个数,求两个数之间所有数据的和
(2)后来用户的需求,变成求两个数之间所有数据的乘积
(3)后来用户的需求,成求两个数之间所有能被3整除的数据的和
这几个方法之间相同的地方,例如:
方法的修饰符方法的返回类型方法的参数列表方法中对数据的遍历
这几个方法之间不同的地方,例如:
方法的名字方法中遍历数据后的核心操作
在实际项目中,用户需求的变动,是很正常的一件事情,所以在上述的案例中,我们不断的增加方法,来解决用户新的需求,但是在整个过程中,我们复制了大量的相同的代码。
方法的名字其实是无关紧要的,它只是在调用时用到的一个标示符,最关键的是对这个对数据的核心操作,也就是代码中核心的行为操作,每种情况由一个具体的 计算行为 来控制,这时候可以将这个核心的行为操作进行抽象,变成一个参数。
将此处的计算行为定义成一个Action接口,接口中有一个action方法
定义不同计算行为的类并且实现Action接口,如下
在java中,不允许孤立的代码存在,要想将行为(核心操作代码)传递给calculate方法,就必须要将这些核心操作代码,包装在一个实现了Action的类中
Calculate方法就可以这样定义
将我们要执行的核心计算操作,定义成一个参数,传给calculate方法,我们可以通过这个参数,给方法传递不同的行为,来实现不同操作,这就是行为参数化,如下
为了减少声明和定义类,可以通过匿名内部类的形式来简化上述调用过程(省去了单独类实现接口的部分),如下
class Test{
pbulic static void main(String[] args){
int result = 0;
result = calculate(3,5,new Action(){
public int action(int result, int i){
return result+next;
}
});
System.out.println(result);
result = calculate(3,4,new Action(){
public int action(int result, int i){
return result + next;
}
});
System.out.println(result);
}
}
2、函数式编程
在前面的一小节中,虽然使用了匿名内部类的方式简化了之前的代码,但是每次调用还是编写了很多相同的代码,如:new Action(){},public int action(int result, int next){}
其实我们真正关心的只有三点:方法中的参数列表,方法中的核心操作代码,方法的返回类型,其他的部分可以直接通过简化省略掉,也就是说传入指定参数,通过核心计算,给出最后结果,函数式编程就是将之前通过传递Action匿名对象的过程,变成一个计算求值的过程,那么这个求值的表达式就是所谓的函数式编程。
再次简化后如下:
class Test{
public static void main(String[] args){
int result = 0;
//忽略掉匿名内部类形式中,不重要的部分
//上述代码课简化成如下Lambda表达式形式
Action action1 = ()->{};
//--------------------------------------------------------
//(2)当函数主体中只有一句代码时,简化时甚至可以把大括号省去
//简化时后如下
Action action2 = ()->System.out.println("world peace!!!");
//----------------------------------------------------------
//(3)当函数主体中有多句代码时,大括号就不能省
//简化后如下
Action action3 = ()->{
int a = 1;
int b = 2;
System.out.println(a + b);
};
}
}
2、函数式接口中,抽象方法有参,无返回值
(1)当接口中抽象方法是一个参数的情况
interface Action{
public void run(int a);
}
class Test{
public static void main(String[] args){
//(1)当函数主体中无代码时
Action action1 = (int a) -> {};
//当参数只有一个时,参数列表部分甚至可以不加小括号,参数类型也可不写,因为即使不写参数类型,JVM运行时也会自动推断这个参数的类型
Action action= a -> {};
//-------------------------------------------------
//(2)当函数主体中只有一句代码时
Action action2 = a ->System.out.println(a);
}
}
(2)当抽象方法中含有多个参数的情况下
interface Action{
public void run(int a, int b);
}
class Test{
public static void main(String[] args){
Action action = (a,b) -> System.out.println(a + b);
}
}
3、函数式接口中抽象方法中无参,有返回值
interface Action{
public int run(){
}
}
class Test{
public static void main(String[] args){
//如果就一句代码,可以省略大括号,return关键字也可省去
Action action1 = ()->1;
//------------------------------------------------------------
//如果函数主体中有多句代码,则都不可省去,如下
Action action2 = () -> {
int num = 10;
return (int)(Math.random()*num);
};
}
}
4、函数式接口中抽象方法有参,有返回值
interface Action{
public int run(int a, int b);
}
class Test{
public static void main(String[] args){
//(1)当函数主体中只有一串代码时
//上面代码简化后如下
Action action1 = (a,b) -> a + b;
//----------------------------------------------------
//(2)当函数主题中有多串代码时
//简化后如下
Action action2 = (a, b) ->{
int num = a + b;
return num;
};
}
}
总结一下
lambda表达式能省略圆括号的情况是当方法参数只有一个的时候,省略大括号的情况是函数主体只有一串代码的时候。表达式中的参数列表可以不声明类型,JVM在运行时可以自动判断。
需要注意的是,当在主体中含有return关键字且可以省略大括号的情况下,那么return关键字、大括号、分号也要一起省略。
lambda补充 类型判断使用Lambda表达式,相当于给函数式接口生成一个实例,但是Lambda表达式本身,并不包含这个接口的任何信息,例如:
之所以Lambda表达式中没有接口的任何信息,JVM还能将其和接口匹配的上,那是因为:
(1)我们在使用Lambda表达式的时候,JVM是会通过上下文自动推断它所属接口类型的
(2)并且接口中只有一个抽象方法,自然也能匹配成功该表达式所对应实现的抽象方法
例如:
JVM还能自动推断出Lambda表达式中参数的类型,例如
如果类中的方法进行了重载,那么在使用Lambda表达式的时候,很可能给它的类型推断带来问题。
例如:
局部变量可以看出,这时候编译报错,因为表达式num -> num>0 对于俩个方法都符合既符合Predicate的实现,也符合Function
的实现
这时候可以做类型转换,来解决这个问题:
如果在Lambda表达式中,使用了局部变量,那么这个局部变量就一定要使用final修饰符进行修饰,这方面的语法要求,和之前学习的匿名内部类保持一致。
注意
JDK1.8中,被匿名内部类、局部内部类、Lambda表达式访问的局部变量,会默认加上final修饰符



