Java8 Lambda 与匿名类

     2023-04-18     57

关键词:

【中文标题】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

Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。很多语言(Groovy、Scala等)从设计之初就支持Lambda表达式。但是java中使用的是匿名内部类代替。最后借助强大的社区力量... 查看详情

java8新特征之lambda

Java8新特征之LambdaLambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。很多语言(Groovy、Scala等)从设计之初就支持Lambda表达式。但是java中使用的是匿名内部类代替。最后借... 查看详情

java8lambda表达式示例

例1、用lambda表达式实现Runnable我开始使用Java8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。看一下Java8之前的runnable实现方法,需要4行代码,而使用lambda表达式只需要一行代码。我们在这... 查看详情

java8新特性之上

一、Lambda表达式Lambda是一个匿名函数。可以写出更简洁、更灵活的代码//匿名内部类Runnabler1=newRunnable(){publicvoidrun(){System.out.println("这是一个匿名内部类");};}//Lambda表达式Runnabler=()->System.out.println("Lambda表达式");操作符:  -& 查看详情

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

...求,写了两篇Java新特性的文章。有小伙伴留言说:感觉Lambda表达式很强大啊!一行代码就能够搞定那么多功能!我想学习下Lambda表达式的语法,可以吗?我的回答是:没问题!这不,Lambda表达式来了!匿名类到Lambda表达式我们... 查看详情

lambda表达式与匿名内部类

*lambda使用条件:*1.使用lambda需要有接口,并且接口只有一个抽象方法。*2.必须要有上下文环境,才能推导出类型。***lambda和匿名内部类区别:*所需类型不同*匿名内部类:可以是接口,也可以是抽象类,还可以是具体类*Lambda表... 查看详情

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

importjava.util.ArrayList;importjava.util.Arrays;importjava.util.List;importjava.util.function.Predicate;/***CreatedbyAdministratoron2017/8/190013.*/publicclassTest{/********************************** 查看详情

08_lambda表达式与匿名内部类的联系与区别

【简述】Lambda表达式是匿名内部类的一种简化,因此它可以取代匿名内部类的作用。 【Lambda表达式与匿名内部类的相同点】1.Lambda表达式和匿名内部类一样,都可以直接访问"effectivelyfinal"的局部变量,以及外部类的成员变量... 查看详情

java8中你可能不知道的一些地方之lambda(代码片段)

Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。很多语言(Groovy、Scala等)从设计之初就支持Lambda表达式。但是java中使用的是匿名内部类代替。最后借助强大的社区力量... 查看详情

java8-lambda表达式

...积极性。在这里,我们会认识Java8中解决这个问题的工具Lambda表达式。它可以让你很简洁第表示一个行为或者传递代码。现在你可以把Lambda表达式看做匿名功能,它基本上没有声明名称的方法,但和匿名类一样,它可以作为参数... 查看详情

java8函数式编程(代码片段)

Lambda表达式函数式接口方法引用与构造器引用StreamAPI接口中的默认方法与静态方法新时间日期API其他新特性速度更快代码更少(增加了新的语法Lambda表达式)强大的StreamAPI便于并行最大化减少空指针异常Optional其中最为核... 查看详情

java8lambda表达式一看就会

...击一个按钮时,我们需要给按钮对象设置按钮响应函数。lambda表达式就可以把函数当做函数的参数,代码(函数)当做数据(形参),这种特性满足上述需求。当要实现只有一个抽象函数的接口时,使用lambda表达式能够更灵活 查看详情

java8lambda表达式(代码片段)

...结Java8时间日期使用 java8中一个非常重要的特性就是lambda表达式,我们可以把它看成是一种闭包,它允许把函数当做参数来使用,是面向函数式编程的思想,一定程度上可以使代码看起来更加简洁。例如以前我们使用匿名内... 查看详情

函数式编程-lambda与stream

...创建线程并启动时可以使用匿名内部类的写法:可以使用Lambda的格式对其进行修改。修改后如下:现有方法定义如下,其中IntBinaryOperator是一个接口。先使用匿名内部类的写法调用该方法。Lambda写法:现有方法定义如下,其中IntP... 查看详情

2020了你还不会java8新特性?lambda表达式及api

lambda表达式为什么要使用lambda表示式在Java中无法将函数座位参数传递给一个方法,也无法返回一个函数的方法。在js中,函数的参数是一个函数。返回值是另一个函数的情况是非常常见的。是一门经典的函数式语言。Java匿名内... 查看详情

java8新特性03lambda表达式

一.Lambda表达式概述Lambda表达式是Java8中最大的变化。它允许我们将一个函数当作方法的参数,或者说把一段代码当作数据使用。很多基于JVM平台的语言一开始就支持Lambda表达式,比如Scala,但是Java语言一直只能使用匿名内部类来... 查看详情

委托与匿名委托

本来是想写一篇《委托与lambda表达式的前世今生》,但仅委托部分已经写了很多内容,于是就此分开关于Lambda表达是的内容后续再写吧。不知道Lambda表达式是谁发明的,只记得第一次接触Lambda表达式是在使用VS2008的时候,那就先... 查看详情

使用lambda表达式和函数式接口predicate(代码片段)

例1、用lambda表达式实现Runnable我开始使用Java8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。看一下Java8之前的runnable实现方法,需要4行代码,而使用lambda表达式只需要一行代码。我们在这... 查看详情