Lambda表达式和匿名内部类

来源:互联网 发布:曲子龙 网络尖刀 编辑:程序博客网 时间:2024/06/16 18:31

Java8由匿名内部类到lambda表达式的转换,除了具体的语法外,二者之间真正的差别是什么。事实上,lambda表达式有时被错误地称为匿名内部类的“语法糖”,这说的只是二者之间只存在简单的语法上的变化。但实际上,二者之间存在很多显著差异,其中有两点对于程序员来说非常重要:

  • 匿名内部类表达式会确保创建一个拥有唯一标识的新对象,而lambda表达式的计算机结果可能有,也可能没有唯一标识,这取决于具体实现。相对于对应的内部类来说,这种灵活性可以让平台使用更为高效的实现策略。
  • 内部类的声明会创建一个新的命名作用域,在这个作用域中,this与super指的是内部类本身的当前实例;相反,lambda表达式并不会引入任何新的命名环境。这样就避免了内部类名称查找的复杂性,名称查找会导致很多小错误,例如想要调用外围实例方法时却错误地调用了内部类实例的Object方法。

1 . 无标识性问题
到目前为止,Java程序的行为总是与对象相关联,以标识,状态和行为为特征。lambda则违背了该规则;虽然它们会共享一些对象的属性,但唯一的用处是表示行为。由于没有状态,因此标识问题就不重要了。语言规范显式表示其是未确定的,唯一的要求就是lambda必须计算出实现了恰当函数式接口的类实例。这么做的意图是赋予平台足够的灵活性来进行优化,如果每个lambda表达式都要拥有唯一标识,那么这种灵活性无法实现。

2 . lambda的作用域规则
就像大多数内部类一样,匿名内部类的作用域规则非常复杂,这是因为它可以引用从父类型继承下来的名字,以及声明在外部类中的名字。lambda表达式则要简单得多,因为它们并不会从父类型中继承名字(这个规则会将父类型(也就是函数接口)中声明的任何名字排除在lambda作用域之外。除了抽象方法之外,接口可以申明静态的final字段、静态嵌套类以及默认方法。它们都不在实现的lambda的作用域之内)。除了参数以外,用在lambda表达式体中的名字的含义与体外面是一样的。例如,像下面这样在lambda中再次声明一个局部变量就是非法的:

void foo() {    final int i = 2;    Runnable r = () -> { int i = 3;} //报错:Lambda expression's local variable i cannot redeclare another local variable defined in an enclosing scope. }

意思是局部变量i在作用域内重复声明了。因为lambda表达式并不会引入任何新的命名环境,lambda体内的作用域和外部类的作用域是一个作用域,因此你相当于在一个作用域内声明了2个相同的局部变量。

参数就像局部声明一样,因为他们呢可以引入新的名称:

IntUnaryOperator iuo = i -> {int j = 3; return i + j;};

lambda参数与lambda体局部声明可以隐藏字段名(也就是说,字段名可能会临时被重新声明为参数或局部变量名)。

class Foo {    Object i, j;    IntUnaryOperator iuo = i -> {int j = 3; return i + j;}}

由于lambda声明就像简单的块一样,因此关键字this与super与外围环境的含义一样:也就是说,他们分别指的是 外围对象及其父类对象。例如,如下程序会向控制台打印2条“Hello, world!”消息:

public class Hello {    Runnable r1 = () -> {System.out.println(this);};    Runnable r2 = () -> {System.out.println(toString());};    public String toString() { return "Hello, world!";};    public static void main(String... args) {        new Hello().r1.run();        new Hello().r2.run();    }}

如果使用匿名内部类而非lambda表达式来写同样的程序,那么它会打印出在内部类的对象上调用toString方法的结果。对于匿名内部类来说,更为常见的访问外围对象当前实例的用法要使用笨拙的语法OuterClass.this,而这对于lambda来说是非常直接的。

0 0
原创粉丝点击