龙空技术网

Java Lambda表达式与匿名内部类的区别

码世界 86

前言:

如今我们对“java中的表达式有哪几类形式”可能比较注意,咱们都想要了解一些“java中的表达式有哪几类形式”的相关内容。那么小编也在网摘上网罗了一些有关“java中的表达式有哪几类形式””的相关资讯,希望朋友们能喜欢,各位老铁们快快来了解一下吧!

匿名类(Anonymous classes)就是一个嵌套在外部类里边且没有名字的类。Lambda表达式在Java8中被引进并用于函数式编程。在某些功能层面,它们通常还能形成替代。接下来先从概念定义和代码案例解释一下它们,然后再从语法,变量作用域,字节码编译,性能方面分别区分下它们。

匿名内部类

定义:匿名内部类是个没有名字的类,可以实现接口和抽象类但是不需要额外创建子类,并且同时实现了类的定义和实例化。

下面我们来看一个例子:

package com.east.demo;public class App {	// 定义一个抽象类	abstract class AbstractClass {		abstract void doHandle();	}	// 定义一个接口	interface SuperInterface {		void execute();	}	public static void main(String[] args) {		// 定义一个实现了抽象类AbstractClass的匿名内部类		new AbstractClass() {			@Override			void doHandle() {				System.out.println("handle...");			}		};		// 定义一个实现了接口SuperInterface的匿名内部类		new SuperInterface() {			@Override			public void execute() {				System.out.println("execute...");			}		};	}}

从上面的例子中我们可以看到,我们创建了一个App外部类,然后在App内部分别定义了一个AbstractClass抽象类和SuperInterface接口,接着就在main函数中分别声明定义和实例化了两个匿名内部类,可以看到我们并没有创建额外的类来实现抽象类和接口。

Lambda 表达式

定义:一个实现了函数式接口的匿名方法且可以作为一个方法参数进行传递。

下面让我们来看一个例子,就以java中现有的Runnable接口为例:

首先看下Runnable作为函数式接口的定义

// @FunctionalInterfacepublic interface Runnable {    public abstract void run();}

然后看下Runnable的具体表现形式

package com.east.demo;public class LambdaExpressionExample {	public static void main(String[] args) {		    // lambda表达式		Thread t1 = new Thread(      // 第9行      () -> System.out.println("ThreadName:" + Thread.currentThread().getName())    );				t1.start();        // 匿名内部类方式    // 第14行    Thread t2 = new Thread(new Runnable() {						@Override			public void run() {				System.out.println("ThreadName:" + Thread.currentThread().getName());			}		});				t2.start();			}}

9行(行数我以注释的方式写在了代码里)可以看出我们使用了一个匿名方法,并且背后的机制可以推断出这就是作为Runnablerun方法,并且可以实现具体逻辑,同时也作为Thread的一个方法参数进行了传递。从第14行开始可以看出我们用匿名内部类的方式也同样实现了打印线程名称的功能,说明两者是可以互换的。既然都能实现同样的功能,那么它们具体的区别在哪里呢,接下来让我们深入讨论下它们的不同之处。

内部类 vs. Lambda表达式

根据上面所述,我们目前大致能知道内部类主要是通过实现了接口或抽象类来定义的(不需要额外创建一个类),而lambda表达式仅仅用在函数式接口上且代码要简洁很多,除了这些区别,下面让我们来看下其它方面的区别。

1.语法层面

匿名类定义好后其实同时完成了两个操作,一个是对类的定义,另一个就是实例化。语法层面我们可以看到是通过new关键字,然后后面紧跟抽象类或接口的名称,类似我们平时实例化一个构造函数一样,同时我们需要实现抽象类和接口中的方法且可以在其中定义成员变量。此外,我们还可以给它声明一个引用变量,这样可以让结构更清晰,并且能进行传递,因此匿名类的结尾一定要以分号结束。如下图:

匿名类语法定义

对应到lambda表达式这块,首先lambda是个没有参数的方法,然后通过箭头符号指向一个方法体。此外,方法参数的具体类型我们也不需要关系,因为这个会在jvm具体运行时去解析。

lambda语法

2.this关键字和变量的作用域

在一个内部类中,this关键字指向的是这个内部类实例本身。但是在lambda表达式中,this就是指向这个lambda表达式存在的这个类中。

内部类和lambda表达式this的作用域

我们可以在内部类里边声明成员变量,而lambda表达式却不可以,因此内部类是有状态的。当然,我们可以在lambda表达式内部定义变量,但是这个变量仅仅是个局部变量。不过对于整个外部类的成员变量来说,它们都是可以访问的。

3.编译层面

在编译层面来看,每个内部类都会生成一个额外的class文件。这个class文件会以class名称命名并带上一个$符号和一个数字。比如,我们有一个App类,然后在里边定义了一个内部类,通过编译后发现会生成一个App$1.class文件,如果你定义了多个内部类会以数字递增的方式生成App$2.class,App$3.class

对于lambda表达式来说,会在class文件中添加一个invokedynamic(一个字节码指令指令。这个指令会找出要调用的函数接口方法。因为在我们编译完lambda表达式后,会在字节码里边生成一个私有的静态方法,这个方法的签名将会映射到对应的函数式接口方法上。

4.性能层面

就性能而言,lambda要比内部类更好。因为内部类会生成额外的class文件在编译期间,因此可能需要花额外的时间会对这个类进行加载和验证。

而对于lambda来说由于动态指令的作用将表达式绑定到对应的函数式接口方法上,因此仅仅第一次调用lamdba表达式比较慢,后续将会很快。

总结

上面展示了内部类和lambda表达式的联系与区别,总的来说,lamdba要更简洁,也更容易理解,而且也更加符合声明式编程。

标签: #java中的表达式有哪几类形式