Java8 Lambda 与匿名类

Posted

技术标签:

【中文标题】Java8 Lambda 与匿名类【英文标题】:Java8 Lambdas vs Anonymous classes 【发布时间】:2014-05-03 11:48:18 【问题描述】:

由于最近发布了 Java8,而且它全新的 lambda 表达式看起来非常酷,我想知道这是否意味着我们习惯的 Anonymous 类的消亡。

我对此进行了一些研究,并找到了一些关于 Lambda 表达式如何系统地替换这些类的很酷的示例,例如 Collection 的 sort 方法,该方法用于获取 Comparator 的匿名实例来执行排序:

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

现在可以使用 Lambda 来完成:

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

而且看起来非常简洁。所以我的问题是,有什么理由继续在 Java8 中使用这些类而不是 Lambdas?

编辑

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

【问题讨论】:

当然,对于所有提供具有副作用的方法的匿名类。 仅供参考,您还可以将比较器构造为:Comparator.comparing(Person::getFirstName),如果getFirstName() 将是返回firstName 的方法。 或者具有多个方法的匿名类,或者... 我很想投票给 close,因为它太宽泛了,尤其是由于 EDIT 之后的附加问题。 一篇关于这个主题的深度文章:infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood 【参考方案1】:

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

我猜 AIC 的大部分用途是提供单个函数的无状态实现,因此可以用 lambda 表达式代替,但 AIC 的其他用途不能使用 lambda。 AIC 将继续存在。

更新

AIC 和 lambda 表达式之间的另一个区别是 AIC 引入了一个新的范围。也就是说,名称是从 AIC 的超类和接口中解析出来的,并且可以隐藏出现在词法封闭环境中的名称。对于 lambda,所有名称都按词法解析。

【讨论】:

Lambda 可以有状态。在这方面,我认为 Lambdas 和 AIC 没有区别。 @nosid AIC 与任何类的实例一样,可以在字段中保存状态,并且该状态可以被类的任何方法访问(并且可能通过该方法改变)。这种状态一直存在,直到对象被 GC'd,即它具有无限的范围,因此它可以在方法调用中持续存在。在遇到 lambda 时捕获 lambda 具有无限范围的唯一状态;这种状态是不可变的。 lambda 中的局部变量是可变的,但它们仅在 lambda 调用正在进行时才存在。 @nosid 啊,单元素数组破解。只是不要尝试从多个线程中使用您的计数器。如果您要在堆上分配一些东西并在 lambda 中捕获它,那么您不妨使用 AIC 并添加一个可以直接改变的字段。以这种方式使用 lambda 可以工作,但是当您可以使用真实对象时,为什么还要麻烦呢? AIC 会创建一个类似 A$1.class 的文件,但 Lambda 不会。我可以在差异中添加这个吗? @UnKnown 这主要是一个实现问题;它不会影响使用 AIC 与 lambda 的程序的方式,这就是这个问题的主要内容。请注意,lambda 表达式确实会生成一个名称类似于 LambdaClass$$Lambda$1/1078694789 的类。但是,这个类是由 lambda 元工厂动态生成的,而不是由 javac 生成的,因此没有对应的 .class 文件。然而,这又是一个实现问题。【参考方案2】:

Lambdas 虽然是一项很棒的功能,但仅适用于 SAM 类型。也就是说,只有一个抽象方法的接口。只要您的接口包含超过 1 个抽象方法,它就会失败。这就是匿名类有用的地方。

所以,不,我们不能忽略匿名类。仅供参考,您的sort() 方法可以更加简化,通过跳过p1p2 的类型声明:

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

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

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

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

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

【讨论】:

我知道,但就可读性而言,我更喜欢长的,否则可能会混淆这些变量的来源。 @AminAbu-Taleb 为什么会让人困惑。这是一个有效的 Lambda 语法。无论如何推断类型。无论如何,这是个人选择。您可以明确指定类型。没有问题。 lambda 和匿名类之间还有一个微妙的区别:匿名类可以直接用新的Java 8 Type Annotations 注释,例如new @MyTypeAnnotation SomeInterface();。这对于 lambda 表达式是不可能的。有关详细信息,请在此处查看我的问题:Annotating the functional interface of a Lambda Expression。【参考方案3】:

匿名类的 Lambda 性能

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

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

Lambdas 在字节码生成方面有所不同,它们更高效,使用 JDK7 自带的 invokedynamic 指令。

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

结果 Lambda 表达式将成为一个静态方法(在运行时创建)。 (stateles 和 statefull case 有一点区别,它们是通过生成的方法参数来解决的)

【讨论】:

每个 lambda 也需要一个新类,但它是在运行时生成的,所以从这个意义上说,lambda 并不比匿名类更高效。 Lambda 是通过 invokedynamic 创建的,这通常比用于创建匿名类的新实例的 invokespecial 慢。所以,从这个意义上说,lambdas 也更慢(然而,JVM 可以优化invokedynamic 调用大部分时间)。 @AndreiTomashpolskiy 1. 请保持礼貌。 2. 阅读编译器工程师的评论:habrahabr.ru/post/313350/comments/#comment_9885460 @ZhekaKozlov,您无需成为编译器工程师即可阅读 JRE 源代码并使用 javap/debugger。您缺少的是 lambda 方法的包装类的生成完全在内存中完成并且几乎没有成本,而实例化 AIC 涉及解析和加载相应的类资源(这意味着 I/O 系统调用)。因此,与已编译的匿名类相比,具有临时类生成的 invokedynamic 速度非常快。 @AndreiTomashpolskiy I/O 不一定很慢【参考方案4】:

有以下区别:

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 表达式中使用 superthis 关键字时,类似的规则适用。在匿名内部类的情况下,这个关键字是指本地范围,而超级关键字是指匿名类的超类。而在 lambda 表达式的情况下,此关键字引用封闭类型的对象,而 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 vs anonymous inner class performance: reducing the load on the ClassLoader?

【讨论】:

【参考方案5】:

Java 8 中的 Lambda 被引入用于函数式编程。在哪里可以避免样板代码。我偶然发现了一篇关于 lambda 的有趣文章。

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

建议将 lambda 函数用于简单的逻辑。如果使用 lambdas 实现复杂的逻辑将是在出现问题时调试代码的开销。

【讨论】:

【参考方案6】:
+----------------------------------+----------------------------------------------------------+---------------------------------------------+
|                                  |                                       Lambdas            |              Anonymous classes              |
+----------------------------------+----------------------------------------------------------+---------------------------------------------+
| Definition                       | An anonymous method that can be created without belonging| An inner class without a name.              |
|                                  | to any class                                             |                                             |
+----------------------------------+----------------------------------------------------------+---------------------------------------------+
| Scope of variables of main class | Available                                                | Not available                               |
| (this and super keywords also)   |                                                          |                                             |
+----------------------------------+----------------------------------------------------------+---------------------------------------------+
| Lines of codes                   | Reduced the lines of code. It’s a short form of          | Have more lines of code compared to lambdas |
|                                  | anonymous class.                                         |                                             |
+----------------------------------+----------------------------------------------------------+---------------------------------------------+
| Criteria for creating            | Needs to be Functional Interface, ie interface with      | Can use interfaces(including Functional     |
|                                  | only one abstract method. Example : Runnable Interface   | interfaces) and abstract classes to create. |
+----------------------------------+----------------------------------------------------------+---------------------------------------------+
| Example:                         | Runnable r = ()->System.out.println("Hello World");;   | Runnable r = new Runnable()                |
|                                  |                                                          |         @Override                           |
|                                  |                                                          |         public void run()                  |
|                                  |                                                          |          System.out.println("Hello World"); |
|                                  |                                                          |                                            |
|                                  |                                                          |     ;                                      |
+----------------------------------+----------------------------------------------------------+---------------------------------------------+

【讨论】:

【参考方案7】: lambda 语法不需要编写 java 可以推断的明显代码。 通过使用invoke dynamiclambda在编译时不会转换回匿名类(Java不必经过创建对象,只关心方法的签名,可以绑定到不创建对象的方法 lambda 更加强调我们想要做的事情,而不是我们在做之前必须做的事情

【讨论】:

【参考方案8】:

匿名类将继续存在,因为 lambda 适用于具有单个抽象方法的函数,但对于所有其他情况,匿名内部类是您的救星。

【讨论】:

【参考方案9】:

Lambdas 表达式仅涵盖匿名内部类的一个非常特殊的情况。你可以认为匿名内部类是 Lambda 表达式的超集

Lambdas Expression ⊂ Anonymous Inner classes

在某些场景中,Anno.内部类可以替换为 Lambda 表达式:

    如果要实现的内部类是函数式接口(只有一个抽象方法)

例如:

Interface A
 public void m1();
 public void m2();


Interface B
 public void m();


Class Temp
 public static void main(String[] args)
  // Anonymous inner class implementation
  A a = new A()
  
   public void m1()
    //
   
   public void m2()
    //
   
  ;
  a.m1(); or a.m2();
 
  // Here B is a functional Interface so we can replace the anonymous class to Lambda Expression
  B b = () =>  ... 
  b.m();
  
 

除此之外,两者的编译风格也有一些不同。对于 Lambda 表达式,编译器不会生成任何其他类,但会为 Anon 生成。内部类

【讨论】:

以上是关于Java8 Lambda 与匿名类的主要内容,如果未能解决你的问题,请参考以下文章

java8--- lambda

JAVA8 in Action:行为参数化,匿名类及lambda表达式的初步认知实例整理

Java8 lambda 表达式示例

Java8新特征之Lambda

Java8新特征之Lambda

Java8新特性Lambda表达式基础语法,都在这儿了!!