Java8 Lambdas与匿名类


111

自从Java8最近发布以来,它的全新lambda表达式看起来真的很酷,我想知道这是否意味着我们曾经习惯的Anonymous类的消亡。

我对此进行了一些研究,发现了一些很酷的示例,这些示例说明Lambda表达式将如何系统地替换这些类,例如Collection的sort方法,该方法用于获取Comparator的Anonymous实例来执行排序:

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

现在可以使用Lambdas完成:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

而且看起来简明扼要。所以我的问题是,是否有任何理由继续在Java8中使用这些类而不是Lambda?

编辑

同样的问题,但方向相反,使用Lambdas代替Anonymous类有什么好处,因为Lambdas只能与单个方法接口一起使用,此新功能仅是仅在少数情况下使用的快捷方式,还是真的有用吗?


5
当然,对于所有提供副作用的匿名类。
tobias_k 2014年

11
仅就您的信息而言,您还可以将比较器构造为:Comparator.comparing(Person::getFirstName),如果getFirstName()返回的方法是firstName
skiwi 2014年

1
或具有多种方法的匿名类,或...
马克·罗特韦尔2014年

1
我很想投票赞成范围太广,尤其是由于EDIT之后的其他问题。
Mark Rotteveel 2014年

1
关于此主题的一篇不错的深入文章:infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Answers:


107

匿名内部类(AIC)可用于创建抽象类或具体类的子类。AIC还可以提供接口的具体实现,包括添加状态(字段)。可以this在其方法主体中使用AIC的实例进行引用,因此可以在其上调用其他方法,其状态可以随时间变化,等等。这些都不适用于lambda。

我猜想AIC的大多数用途是提供单个函数的无状态实现,因此可以用lambda表达式代替,但是AIC的其他用途不能使用lambda。AIC在这里停留。

更新

AIC和lambda表达式之间的另一个区别是AIC引入了新的范围。也就是说,名称是通过AIC的超类和接口解析的,可以遮盖出现在词法包围环境中的名称。对于lambda,所有名称都按词法解析。


1
Lambda可以有状态。在这方面,我认为Lambda和AIC之间没有区别。
nosid 2014年

1
@nosid AIC像任何类的实例一样,可以在字段中保存状态,并且该状态可以被该类的任何方法访问(并且可能被其可变)。该状态一直存在,直到对象被GC处理为止(即,它具有不确定的范围),因此它可以在方法调用之间持续存在。遇到lambda时,将捕获到lambda具有无限范围的唯一状态;这种状态是不可变的。Lambda中的局部变量是可变的,但仅在Lambda调用正在进行时才存在。
斯图尔特(Stuart Marks)2014年

1
@nosid啊,单元素数组黑客。只是不要尝试从多个线程使用计数器。如果要在堆上分配一些东西并将其捕获到lambda中,则最好使用AIC并添加一个可以直接变异的字段。以这种方式使用lambda可以工作,但是当您可以使用真实对象时为什么还要打扰呢?
斯图尔特(Stuart Marks)2014年

2
AIC将创建类似这样的文件,A$1.class但是Lambda不会。我可以在差异中添加它吗?
阿西夫·穆什塔克

2
@UnKnown主要是实现方面的问题;它不会影响使用AIC与Lambda进行程序编程的方式,而这正是这个问题的主要目的。请注意,lambda表达式的确会生成一个名称类似的类LambdaClass$$Lambda$1/1078694789。但是,此类是由lambda元工厂动态生成的,而不是由即时生成的javac,因此没有相应的.class文件。但是,这又是一个实现问题。
斯图尔特·马克

60

Lambdas虽然功能强大,但仅适用于SAM类型。也就是说,仅使用一个抽象方法进行接口。一旦您的接口包含多个抽象方法,它将失败。那是匿名类有用的地方。

因此,不,我们不能仅仅忽略匿名类。而就通知你,你的sort()方法可以更简化,通过跳过类型声明p1p2

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

您也可以在此处使用方法参考。您可以compareByFirstName()Person类中添加方法,然后使用:

Collections.sort(personList, Person::compareByFirstName);

或者,为添加一个getter firstName,直接Comparator从from Comparator.comparing()方法获取:

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
我知道,但就可读性而言,我更喜欢长篇文章,因为否则可能很难弄清楚这些变量的来源。
阿敏·阿布·塔莱布

@ AminAbu-Taleb为什么会令人困惑。那是有效的Lambda语法。无论如何,类型都是推断的。无论如何,这是个人选择。您可以明确指定类型。没有问题。
罗希特·贾因

1
lambda和匿名类之间还有另一个细微的区别:匿名类可以使用新的Java 8类型注解直接注解,例如new @MyTypeAnnotation SomeInterface(){};。对于lambda表达式,这是不可能的。有关详细信息,请在此处查看我的问题:注释Lambda Expression的功能接口
Balder 2014年

36

匿名类的Lambda性能

启动应用程序时,必须加载并验证每个类文件。

匿名类由编译器作为给定类或接口的新子类型处理,因此将为每个匿名类生成一个新的类文件。

Lambda在字节码生成方面有所不同,它们效率更高,可以使用JDK7随附的invokedynamic指令。

对于Lambda,此指令用于将字节码中的lambda表达式转换延迟到运行时。(该指令仅在第一次被调用)

结果,Lambda表达式将成为静态方法(在运行时创建)。(stateles和statefull情况之间存在很小的差异,它们通过生成的方法参数来解决)


每个lambda还需要一个新类,但是它是在运行时生成的,因此从这个意义上讲,lambda并不比匿名类更有效。创建Lambda的invokedynamic速度通常比invokespecial创建匿名类的新实例慢。因此,从这个意义上讲,lambda速度也较慢(但是,JVM invokedynamic大部分时间可以优化调用)。
ZhekaKozlov'5

3
@AndreiTomashpolskiy 1.请保持礼貌。2.阅读编译工程师的评论:habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ZhekaKozlov,您不需要成为编译器工程师即可阅读JRE源代码并使用javap / debugger。您所缺少的是,为lambda方法生成包装类的过程完全在内存中完成,并且几乎没有成本,而实例化AIC涉及解析和加载相应的类资源(这意味着I / O系统调用)。因此,invokedynamic与已编译的匿名类相比,临时类的生成速度很快。
Andrei Tomashpolskiy

@AndreiTomashpolskiy I / O不一定很慢
ZhekaKozlov

14

有以下区别:

1)语法

与匿名内部类(AIC)相比,Lambda表达式看起来很整洁

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2)范围

匿名内部类是一个类,这意味着它具有在内部类内部定义的变量的范围。

lambda表达式本身并不是范围,而是封闭范围的一部分。

在匿名内部内部类和lambda表达式中使用时,super关键字也适用类似的规则。如果是匿名内部类,则此关键字指本地范围,而super关键字指匿名类的超类。在使用lambda表达式的情况下,此关键字指的是封闭类型的对象,而super则指的是封闭类的super类。

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3)表现

在运行时,匿名内部类需要类加载,内存分配和对象初始化以及非静态方法的调用,而lambda表达式是纯编译时活动,并且在运行时不会产生额外的开销。因此,与匿名内部类相比,lambda表达的性能更好。**

**我确实意识到这一点并不完全正确。请参阅以下问题以获取详细信息。 Lambda与匿名内部类的性能:减轻ClassLoader的负担?



0

匿名类仍然存在,因为lambda非常适合具有单个抽象方法的函数,但在所有其他情况下,匿名内部类都是您的救星。


-1
  • lambda语法不需要编写Java可以推断出的显而易见的代码。
  • 通过使用invoke dynamiclambda不会在编译期间转换回匿名类(Java不必经历创建对象的过程,只需关心方法的签名,就可以绑定到方法而无需创建对象
  • lambda更加强调我们想要做的事情,而不是我们必须要做的事情
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.