Java 中 Lambda 与匿名内部类的区别
Java 中 Lambda 与匿名内部类的区别
事情的起源
为什么会突然想着写这个知识点呢?因为今天在开发的过程中踩坑了。在进行业务开发的过程中,遇到了下面一段代码(为了方便理解,业务逻辑被简化掉了):
public class Service {
public ExecutorService executorService = Executors.newSingleThreadExecutor();
public void businessMethodWithLambda() {
executorService.submit(() -> XXXUtils.execute(this));
}
public void businessLogic() {
System.out.println("执行业务逻辑");
}
}
public class XXXUtils {
public static void execute(Object object) {
try {
Service service = (Service) object;
service.businessLogic();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这段代码调用 businessMethodWithLambda 方法,提交一个任务到线程池中,使用 Lambda 的形式。看起来没什么毛病,但是采用 Lambda 的形式提交,最终提交的是 Runnable 的一个具体实现。其中只有 run 方法的逻辑,这回导致在执行 run 方法的过程中,会丢失提交任务的线程的上下文,导致 Trace 链路丢失。
所以我手一抖给改成了下面的形式:
public class Service {
public ExecutorService executorService = Executors.newSingleThreadExecutor();
public void businessMethodWithAnonymousInnerClass() {
executorService.submit(new AbstractRunnable() {
@Override
public void doRun() {
XXXUtils.execute(this);
}
});
}
public void businessLogic() {
System.out.println("执行业务逻辑");
}
}
public abstract class AbstractRunnable implements Runnable {
private void init() {
System.out.println("执行一些初始化");
}
@Override
public void run() {
init();
doRun();
}
private void clean(){
System.out.println("执行一些清理动作");
}
/**
* 具体业务逻辑
*/
public abstract void doRun();
}
提交任务的时候,改为匿名类提交的方式,提交了一个 AbstractRunnable 的具体实现。在 AbstractRunnable 的 run 方法中在真正执行业务逻辑前,会执行一些初始化操作,设置线程上下文之类的。在业务逻辑执行完成之后,会执行一些清理操作。
看起来一切都辣么美好,然后反手运行,然而现实予以痛击,一个 ClassCastException 直接抛出:
java.lang.ClassCastException: com.mingming.lamba.Service1.doRun(Service.java:13) at com.mingming.lamba.AbstractRunnable.run(AbstractRunnable.java:11) at java.util.concurrent.ExecutorsWorker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
可以看到异常提示 com.mingming.lamba.Service1 实例是个什么东西,我在代码中并没有生成这个类呀?
回头想想这一波有什么改动呢?唯一的改动就是把 Lambda 的提交方式改成了匿名类的提交方式,那么一定是 Lambda 和匿名类有一些区别。
Lambda 与匿名类的区别
匿名类 | Lambda |
---|---|
没有名字的类 | 没有名称的方法(匿名函数) |
可以实现拥有任意方法的接口或者抽象类 | 只能使用在仅有一个抽象方法的接口中 |
可以实例化匿名内部类 | Lambda 表达式无法实例化 |
每当我们创建对象时,内存分配都是按需的 | 它驻留在JVM的永久内存中 |
在匿名内部类内部,“ this”始终是指当前匿名内部类对象,而不是外部对象 | 在Lambda表达式内部,“ this”始终引用当前的外部类对象,即包围类对象 |
如果我们要处理多种方法,这是最佳选择 | 如果我们要处理接口,这是最佳选择 |
匿名类可以具有实例变量和方法局部变量 | Lambda表达式只能具有局部变量 |
在编译时,将生成一个单独的.class文件 | 在编译时,不会生成单独的.class文件。只是将其转换为外部类的私有方法 |
可以看到在匿名类中 this 始终是指向的匿名内部类对象,而 Lambda 指向的是包围类对象。并且匿名内部类在编译之后会生成一个单独的 .class 文件,那么这个匿名内部类生成的文件的类名称是什么呢?
可以看到生成的匿名内部类正是前面异常中提到的 Service$1,问题的原因找到了,那么有什么办法能获取到匿名类对应的外部类的实例吗?
可以看到在匿名类实例中持有一个 this$0 变量,指向 Service 外部类,那我们如何获取这个变量呢?Java 提供了一个语法用来获取部类对象:
public class Service {
public ExecutorService executorService = Executors.newSingleThreadExecutor();
public void businessMethodWithAnonymousInnerClass() {
executorService.submit(new AbstractRunnable() {
@Override
public void doRun() {
XXXUtils.execute(Service.this);
}
});
}
public void businessLogic() {
System.out.println("执行业务逻辑");
}
}
通过 ClassName.this 的方式就可以简便的获取到外部类对象。
最后
魔鬼在细节,还有许多知识需要学习啊。