美文网首页程序员
Lambda表达式语法简介

Lambda表达式语法简介

作者: 巡山的喽罗 | 来源:发表于2016-06-04 20:34 被阅读0次

初识Lambda已经大致了解Lambda的使用。
其实可以把Java Lambda表达式简单地看做是一个匿名方法,所以他的语法构成就很像一个方法的定义。


public class Calculator {
  
    interface IntegerMath {
        int operation(int a, int b);   
    }
  
    public int operateBinary(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }
 
    public static void main(String... args) {
        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction));    
    }
}

additionsubtraction就可以看做是IntegerMath省略了operation名称的方法。

表达式组成

  1. 括号以及括号里用逗号分隔的参数列表

仅有一个参数的可以省略括号。

  1. ->符号
  2. 花括号以及花括号里的语句

仅有一条语句时可以省略花括号,并且这条语句的值将作为return返回值。

更严谨的语法规范可以参考Java8语言规范:Lambda表达式

作用域

Lambda的作用域与类和其它匿名类基本一致,也可以进行变量捕捉,但是Lambda不存在类和匿名类的Shadowing问题。


import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {

            Consumer<Integer> myConsumer = (y) -> {
                System.out.println("x = " + x);
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };

            myConsumer.accept(9);
        }
    }

    public static void main(String... args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

执行结果:

x = 23
y = 9
this.x = 1
LambdaScopeTest.this.x = 0

Lambda可以访问LambdaScopeTest的成员变量、FirstLevel的成员变量、methodInFirstLevel的参数变量。如果使用Consumer的匿名类,那么内部类FirstLevel成员变量无法直接访问;如果匿名类方法的参数与外部的局部作用域某个变量同名,那么局部作用域的同名变量同样无法访问。


Consumer<Integer> a = new Consumer<Integer>() {
    @Override
    public void accept(Integer x) {
        System.out.println("x = " + x);
        // System.out.println("this.x = " + this.x); 编译错误,无法访问
        System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x);
    }
};

在局部作用域中,Lambda表达式的参数列表是不能与局部作用域中的变量同名,因为Lambda没有像匿名类那样产生新的类作用域。

在myConsumer这个表达式中定义参数名称为x


void methodInFirstLevel(int x) {

    Consumer<Integer> myConsumer = (x) -> {
        System.out.println("x = " + x);
        System.out.println("this.x = " + this.x);
        System.out.println("LambdaScopeTest.this.x = " + LambdaScopeTest.this.x);
    };

    myConsumer.accept(9);
}

由于参数列表中变量名称与局部作用域变量同名,就产生一个编译错误:

variable x is already defined in method methodInFirstLevel(int)

如何确定表达式类型

可以把表达式赋值给指定类型的变量

IntegerMath addition = (a, b) -> a + b;
IntegerMath subtraction = (a, b) -> a - b;

或者直接作为参数传入调用方法


myApp.operateBinary(40, 2,  (a, b) -> a + b)

additionoperateBinary方法中的参数都需要一个IntegerMath类型,变量或者方法参数期待得到的类型称作目标类型(Target Type)。编译器可以通过上下文环境确定Lambda表达式的类型,上述就是通过表达式中的参数变量定义和返回值推导表达式的类型。

编译器通过以下上下文识别Lambda表达式类型:

  • 变量声明
  • 赋值
  • return语句
  • 数组初始化
  • 方法或构造方法参数
  • Lambda表达式体
  • 条件语句?:
  • 类型转换

对于在方法参数中出现的表达式,编译器需要在以下两个特性中确定表达式目标类型:

  • 方法重载
  • 类型参数接口

invoke方法重载,一个参数为Runnable另一个为Callable


void invoke(Runnable r) {
    r.run();
}

<T> T invoke(Callable<T> c) {
    return c.call();
}

public interface Runnable {
    void run();
}

public interface Callable<V> {
    V call();
}

假如传入一个Lambda表达式调用invoke


String s = invoke(() -> "done");

那么这个时候到底是调用invoke(Runnable)还是invoke(Callable)
最终会调用invoke(Callable),因为Runnable.run没有返回值。所以表达式() -> "done"的类型为Callable<T>

Lambda表达式序列化

目标类型和捕捉到的参数是可序列化的,那么Lambda也就可以序列化。不过和匿名类一样,Lambda序列化的坑不小。


参考资料

  1. Learning the Java Language - Lambda Expressions
  2. The Java Language Specification, Java SE 8 Edition

相关文章

网友评论

    本文标题:Lambda表达式语法简介

    本文链接:https://www.haomeiwen.com/subject/hvbldttx.html