目录
一、Lambda 表达式介绍
二、Lambda表达式语法
1、语法
2、类型推断
3、变量作用域
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
Lambda表达式的本质:作为函数式接口的实例
如果一个接口中,只声明了一个抽象方法,则此接口称为函数式接口,我们可以在一个接口上使用 @FunctionalInterface 注解 ,这样做可以检查它是否是一个函数式接口。
所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
语法格式一:无参,无返回值
@Test
public void test1(){
Runnable r1 = new Runnable(){
@Override
public void run() {
System.out.println("111111111");
}
};
r1.run();
Runnable r2 = () -> System.out.println("22222222222");
r2.run();
}
语法格式二:Lambda 需要一个参数,但是没有返回值。
@Test
public void test2(){
Consumer con1 = new Consumer(){
@Override
public void accept(String s) {
System.out.println(s);
}
};
con1.accept("123456789");
Consumer con2 = (String s) -> System.out.println(s);
con2.accept("123");
}
语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
@Test
public void test3(){
Consumer con1 = (String s) -> System.out.println(s);
con1.accept("123");
Consumer con2 = (s) -> System.out.println(s);
con2.accept("123");
}
语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
@Test
public void test4(){
Consumer con1 = (s) -> System.out.println(s);
con1.accept("123");
Consumer con2 = s -> System.out.println(s);
con2.accept("123");
}
语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
@Test
public void test5(){
Comparator com1 = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(com1.compare(12,34));
Comparator com2 = (o1,o2) ->{
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(com2.compare(23,45));
}
语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
@Test
public void test6(){
Comparator com1 = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
System.out.println(com1.compare(23,45));
Comparator com2 = (o1,o2) -> o1.compareTo(o2);
System.out.println(com2.compare(12,21));
}
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
public class LambdaTest3 {
public static void main(String[] args) {
final int num = 5;
Consumer con = (o) -> System.out.println(o + num);
con.accept(1);
}
}
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
报错信息:Local variable num defined in an enclosing scope must be final or effectively
这句话的意思是lambda 表达式中使用的变量应该是 final 或者有效的 final
其实在 Java 8 之前,匿名类中如果要访问局部变量的话,那个局部变量必须显式的声明为 final
,如下代码在 Java 7 中是编译不过的:
public static void main(String[] args) {
int num = 5;
new Consumer(){
@Override
public void accept(Integer integer) {
System.out.println(integer + num);
}
};
num = 8;
}
我们知道,lambda 表达式是由匿名内部类演变过来的
,它们的作用都是实现接口方法,于是类比匿名内部类,lambda 表达式中使用的变量也需要是 final 类型。
但是 num 并没有声明为 final 类型,然而代码却能够编译通过,这是因为 Java 8 之后,在内部类或 Lambda 表达式中访问的局部变量,如果不是 final 类型的话,编译器自动加上 final 修饰符,即 Java8 新特性:effectively final。
追究其根本原因就是作用域中变量的生命周期导致的,首先需要知道的一点是: 内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。
这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的"copy"。这样就好像延长了局部变量的生命周期。
问题又出现了:将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?
就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";
Comparator comparator = (first, second) -> Integer.compare(first.length(),second.length()); //编译会出错
参考文章:为什么局部内部类和匿名内部类只能访问 final 的局部变量? | 菜鸟教程 (runoob.com)