理解lambda表达式之前,需要先理解行为参数化和函数式编程的概念。
观察如下的代码:
(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);
}
}
在前面的一小节中,虽然使用了匿名内部类的方式简化了之前的代码,但是每次调用还是编写了很多相同的代码,如:new Action(){},public int action(int result, int next){}
其实我们真正关心的只有三点:方法中的参数列表,方法中的核心操作代码,方法的返回类型,其他的部分可以直接通过简化省略掉,也就是说传入指定参数,通过核心计算,给出最后结果,函数式编程就是将之前通过传递Action匿名对象的过程,变成一个计算求值的过程,那么这个求值的表达式就是所谓的函数式编程。
再次简化后如下:
class Test{
public static void main(String[] args){
int result = 0;
/*
result = calculate(3,5,new Action(){
public int action(int result, int i){
return result+next;
}
});
*/
//忽略掉匿名内部类形式中,不重要的部分
/*
Action add = (int result, int i) ->{
return result + i;
};
//进一步简化
Action add = (result, i) ->result + i;
result = calculate(3,5,add);
System.out.println(result);
}
}
函数式编程是面向数学的抽象,将计算过程描述为一种表达式求值。简单说,函数式程序就是一个表达式。
严格意义上的表达式,就是由数据和操作符按照一定的规则,组合在一起形成的序列,并且所有的表达式都是有返回结果的,这是这里所说的表达式和代码语句的最大的区别。但在java中,是允许函数式编程中没有任何返回值的,因为java中有关键字void。
上面代码中的(result,next)->result+next; 就是java中函数式编程的一些体现,本质意义就是根据函数的入参,通过表达式的计算,最后返回一个结果,在JDK1.8中这一部分就叫Lambda表达式。
Lambda表达式是JDK1.8新增的一种语法,以确保在java代码中可以支持函数式编程,让代码的表示含义更简单。
Lambda表达式,可以用来表示一个函数,它只关注函数的参数列表,函数主体、返回类型,并且可以将此函数作为一个参数,进行传递。
在java中,Lambda表达式还有另一个存在的意义,那就是作为一个接口的实现类对象。
例如:
interface Action{
public int test(String str);
)
class Test{
public static void main(String[] args){
Action a = (str)->str.length();
System.out.println(a.test("hello"));
)
)
Lambda表达式虽然可以通过(参数列表,函数主体、返回类型)三部分来表示一个具体的函数操作,但是它必须是依托在一个接口才行,所以Lambda表达式就是对接口中抽象方法的实现。
需要注意的是,并不是任意接口都可以使用Lambda表达式来进行简化实现的,例如当一个接口中带有多个未实现的方法时,Lambda表达式就不能在这种情况下使用了,一个Lambda表达式,只是描述了一个函数的参数列表、函数主体、返回类型,那么它顶多是对接口中的一个抽象方法的实现,无法实现接口中多个抽象方法,所以,接口中有且只有一个抽象方法的时候,才可以使用Lambda表达式来对其进行实现
有且只有一个抽象方法的接口,就是函数式接口。该接口中,也允许有其他的默认方法和静态方法。
当一个含有一个抽象方法的接口又继承了另一个含有一个抽象方法的接口时,这个接口就不是函数式接口了。
该注解是用来检查被标注的接口是不是一个函数式接口,如果不是,那么编译器报错。但该注解不是必须要用的,它只是会让编辑器帮我们检查一下而已,以免出现接口中抽象方法的个数不是1的情况.
Lambda表达式的格式为:( ) -> { }
针对函数式接口中抽象方法有无参数,有无返回值的不同情况,lambda表达式也可再进一步的省略。
1、函数式接口中抽象方法无参,无返回值时
//这是一个函数式接口
interface Action{
public void run();
}
class Test{
public static void main(String[] args){
//(1)当函数主体中无任何代码句式时
/*
Action action1 = new Action(){
public void run(){
//空
}
};
*/
//上述代码课简化成如下Lambda表达式形式
Action action1 = ()->{};
//--------------------------------------------------------
//(2)当函数主体中只有一句代码时,简化时甚至可以把大括号省去
/*
Action action2 = new Action(){
public void run(){
System.out.println("world peace!!!");
}
};
*/
//简化时后如下
Action action2 = ()->System.out.println("world peace!!!");
//----------------------------------------------------------
//(3)当函数主体中有多句代码时,大括号就不能省
/*
Action action3 = new Action(){
public void run(){
int a = 1;
int b = 2;
System.out.println(a + b);
}
};
*/
//简化后如下
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 = new Action(){
public void run(int a){
}
};
*/
Action action1 = (int a) -> {};
//当参数只有一个时,参数列表部分甚至可以不加小括号,参数类型也可不写,因为即使不写参数类型,JVM运行时也会自动推断这个参数的类型
Action action= a -> {};
//-------------------------------------------------
//(2)当函数主体中只有一句代码时
/*
Action action2 = new Action(){
public void run(int a){
System.out.println(a);
}
};
*/
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 = new Action(){
public void run(int a, int b){
System.out.println(a+b);
}
};
*/
Action action = (a,b) -> System.out.println(a + b);
}
}
3、函数式接口中抽象方法中无参,有返回值
interface Action{
public int run(){
}
}
class Test{
public static void main(String[] args){
/*
Action action1 = new Action(){
public void run(){
return 1;
}
};
*/
//如果就一句代码,可以省略大括号,return关键字也可省去
Action action1 = ()->1;
//------------------------------------------------------------
/*
Action action1 = new Action(){
public void run(){
int num = 10;
return (int)(Math.random()*num);
}
};
*/
//如果函数主体中有多句代码,则都不可省去,如下
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 = new Action(){
public int run(){
return a + b;
}
};
*/
//上面代码简化后如下
Action action1 = (a,b) -> a + b;
//----------------------------------------------------
//(2)当函数主题中有多串代码时
/*
Action action1 = new Action(){
public int run(){
int num = a + b;
return num;
}
};
*/
//简化后如下
Action action2 = (a, b) ->{
int num = a + b;
return num;
};
}
}
总结一下
lambda表达式能省略圆括号的情况是当方法参数只有一个的时候,省略大括号的情况是函数主体只有一串代码的时候。表达式中的参数列表可以不声明类型,JVM在运行时可以自动判断。
需要注意的是,当在主体中含有return关键字且可以省略大括号的情况下,那么return关键字、大括号、分号也要一起省略。
使用Lambda表达式,相当于给函数式接口生成一个实例,但是Lambda表达式本身,并不包含这个接口的任何信息,例如:
之所以Lambda表达式中没有接口的任何信息,JVM还能将其和接口匹配的上,那是因为:
(1)我们在使用Lambda表达式的时候,JVM是会通过上下文自动推断它所属接口类型的
(2)并且接口中只有一个抽象方法,自然也能匹配成功该表达式所对应实现的抽象方法
例如:
JVM还能自动推断出Lambda表达式中参数的类型,例如
如果类中的方法进行了重载,那么在使用Lambda表达式的时候,很可能给它的类型推断带来问题。
例如:
可以看出,这时候编译报错,因为表达式num -> num>0 对于俩个方法都符合既符合Predicate的实现,也符合Function
的实现
这时候可以做类型转换,来解决这个问题:
如果在Lambda表达式中,使用了局部变量,那么这个局部变量就一定要使用final修饰符进行修饰,这方面的语法要求,和之前学习的匿名内部类保持一致。
注意
JDK1.8中,被匿名内部类、局部内部类、Lambda表达式访问的局部变量,会默认加上final修饰符