你是否了解 Java 中的 Lambda 表达式?以及它的变量捕获机制?

453 阅读5分钟

1. Lambda 表达式

1.1 语法

  Lambda表达式是Java SE 8引入的一种函数式编程语法,它的语法形式是:

(parameters) -> expression

(parameters) -> { statements; }
  • parameters是参数列表,可以是空的或者包含一个或多个参数。如果有多个参数,用逗号分隔。
  • ->是Lambda运算符,将参数列表和Lambda表达式的主体分开。
  • expression是一个表达式,可以是常量、变量、方法调用、操作符、Lambda表达式或其他表达式。
  • { }中的语句是Lambda表达式的主体,可以是一个或多个语句,也可以是一个代码块,是函数式接口里方法的实现。
// 1. 不需要参数,返回值为 2
() -> 2
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的和
(x, y) -> x + y
// 4. 接收2个int型整数,返回他们的乘积
(int x, int y) -> x * y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

1.2 函数式接口

  Lambda表达式必须搭配函数式接口使用。

  函数式接口定义:一个接口有且只有一个抽象方法 。

形如下面的接口:

interface FunctionalInterface{

    void print();
    
    //可以加 default 方法,只要保证接口中只有一个抽象方法就行了
    default void test2() {
        System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
    }
}

1.3 Lambda 的使用

  现有如下的函数接口:

//无返回值无参数
interface NoParameterNoReturn {
    void test();
}

//无返回值一个参数
interface OneParameterNoReturn {
    void test(int a);
}

//无返回值多个参数
interface MoreParameterNoReturn {
    void test(int a,int b);
}

//有返回值无参数
interface NoParameterReturn {
    int test();
}

//有返回值一个参数
interface OneParameterReturn {
    int test(int a);
} 

//有返回值多参数
interface MoreParameterReturn {
    int test(int a,int b);
}
public static void main(String[] args) {

    //无返回值无参数
    NoParameterNoReturn noParameterNoReturn = ()-> System.out.println("通过Lambda的方式:这是 test 方法");
    NoParameterNoReturn noParameterNoReturn1 = new NoParameterNoReturn() {
        @Override
        public void test() {
            System.out.println("通过匿名内部类的方式:这是 test 方法");
        }
    };
    noParameterNoReturn.test();
    noParameterNoReturn1.test();
}
结果:
通过Lambda的方式:这是 test 方法
通过匿名内部类的方式:这是 test 方法

   Lambda 与匿名内部类非常相似,它们的作用都是创建一个匿名类并重写里面的方法,但是 Lambda 的形式比较简单,但是只适用于函数接口;而匿名内部类可用于接口中多个抽象方法的情况。

public class Main {
    public static void main(String[] args) {

        //无返回值无参数
        NoParameterNoReturn noParameterNoReturn = ()-> System.out.println("通过Lambda的方式:这是 test 方法");
        noParameterNoReturn.test();

        //无返回值一个参数,可以省略:a->System.out.println(a);
        OneParameterNoReturn oneParameterNoReturn = (int a)-> System.out.println(a);
        oneParameterNoReturn.test(100);

        //无返回值多个参数,int 可以省略:(a,b)-> System.out.println("a: " + a+" " + "b: " + b);
        MoreParameterNoReturn moreParameterNoReturn = (int a,int b)-> System.out.println("a: " + a+" " + "b: " + b);
        moreParameterNoReturn.test(200,300);

        //有返回值无参数,return 可以省略: ()->100;
        NoParameterReturn noParameterReturn = ()-> {
            int a = 100;
            return a;
        };
        System.out.println(noParameterReturn.test());

        //有返回值多参数
        MoreParameterReturn moreParameterReturn = (int a,int b)->{
            return a + b;
        };
        System.out.println(moreParameterReturn.test(100, 200));
    }
}
  1. 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
  2. 参数的小括号里面只有一个参数,那么小括号可以省略。
  3. 如果方法体当中只有一句代码,那么大括号可以省略。
  4. 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字。

2. 变量捕获

2.1 匿名内部类的变量捕获

  所谓变量捕获就是:匿名内部类中访问所在方法或代码块中的局部变量,这个过程被称为“变量捕获”。

public static void main(String[] args) {
    //变量捕获
    int a = 100;
    NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
        @Override
        public void test() {
            //捕获 a 变量
            System.out.println(a);   
        }
    };
    noParameterNoReturn.test();
}
结果:
100

  但是,只有在局部变量是“有效的最终变量”时,匿名内部类才能够引用它。如果局部变量不是有效的最终变量,则无法在匿名内部类中引用它,会导致编译错误。

  有效的最终变量指的是一个在生命周期中没有被修改过的局部变量,它可以被认为是一个常量。

image.png

2.2 Lambda 的变量捕获

  同样的,Lambda 的变量捕获也是只有在局部变量是“有效的最终变量”时,才能够引用它。

public static void main(String[] args) {
    //变量捕获
    int a = 100;
    
    NoParameterNoReturn noParameterNoReturn = ()->{
        //捕获 a 变量
        System.out.println(a);
    };
    noParameterNoReturn.test();
}
结果:
100

image.png

2.3 匿名内部类的变量捕获 与 Lambda 的变量捕获的区别

  区别:Lambda表达式可以捕获外面的this,而匿名内部类无法直接捕获外面的this。

  在Lambda表达式中,this关键字引用的是Lambda表达式所在的类的实例,而不是Lambda表达式内部的类的实例。这个特性被称为“闭包”。

  匿名内部类中的this关键字引用的是匿名内部类的实例,而不是外部类的实例。

public class Student {
    //外部类的属性
    String name = "Student";
    int age;

     NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
        //内部类自己的属性
        String name = "hello";
        @Override
        public void test() {
            System.out.println(this.name);
        }
    };

     NoParameterNoReturn noParameterNoReturn2 = ()->{
        String name = "hello";
        System.out.println(this.name);
    };

    public static void main(String[] args) {
        Student student = new Student();
        student.noParameterNoReturn.test();
        student.noParameterNoReturn2.test();
    }
}

结果:

image.png

  如果想让匿名内部类的捕获外面的 this,就需要使用外部类的类名或对象引用来访问。。

public class Student {
    //外部类的属性
    String name = "Student";
    int age;

     NoParameterNoReturn noParameterNoReturn = new NoParameterNoReturn() {
        //内部类自己的属性
        String name = "hello";
        @Override
        public void test() {
            System.out.println(Student.this.name);
        }
    };
}
结果:
Student

3. Lambda 在集合当中的使用

对应的接 口新增的方法
CollectionremoveIf() spliterator() stream() parallelStream() forEach()
ListreplaceAll() sort()
MapgetOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()

3.1 Collection 接口中的 forEach() 方法

  这个方法是在接口 Iterable 当中的:

image.png

  这个方法表示用于对集合中的每个元素执行指定的操作。

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");

        //匿名内部类 遍历 list
        list.forEach(new Consumer<String>() {
            //执行的操作
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

        System.out.println("---------------------");
        
        //Lambda 遍历 list
        list.forEach((String s)->{
            System.out.println(s);
        });
    }
}
结果:
Hello
bit
hello
lambda
---------------------
Hello
bit
hello
lambda

3.2 List 接口中的 sort() 方法

public class Test {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
        //匿名内部类
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
        System.out.println(list);


        List<String> list2 = new ArrayList<>();
        list2.add("hello");
        list2.add("lambda");        
        list2.add("Hello");
        list2.add("bit");
        //Lambda
        list2.sort((o1,o2)->o1.compareTo(o2));
        System.out.println(list2);
    }
}

3.3 Map接口的 forEach() 方法

  这个方法的源码:

image.png

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "hello");
    map.put(2, "bit");
    map.put(3, "hello");
    map.put(4, "lambda");
    //匿名内部类
    map.forEach(new BiConsumer<Integer, String>() {
        @Override
        public void accept(Integer integer, String s) {
            System.out.println(integer + "=" + s);
        }
    });
    System.out.println();
    
    //Lambda
    map.forEach((integer,s)->{System.out.println(integer + "=" + s);});
}
结果:
1=hello
2=bit
3=hello
4=lambda

1=hello
2=bit
3=hello
4=lambda