Java 匿名内部类
最近看了一些程序语言的设计,语言的本质等等···接触到了一些神奇的名词,协变与逆变(Covariance and contravariance)
等等。
于是,我觉得越来越不会写代码了,有时候不断的会想,为什么是这样的?
来看看 Java 的匿名内部类吧···
常见的匿名内部类写法
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
}
});
这是在 Android 中十分常见的写法,这个 new OnClickListener 就是匿名内部类,一开始我也很好奇这个名称,为什么叫 匿名内部类
????
不过在我知道具体的原理之后,我觉得这个名字起得真不错~~
new 关键字
一开始接触 Java 的时候,我们就知道,抽象类和接口不能实例化,而 new 关键字就是负责实例化的。好了,矛盾了。
问题一:OnClickListener 是个接口,为什么我们可以 new
问题二:创建对象,JVM 总要先加载这个类,不然如何知道这个类有什么方法、成员变量呢?不然怎么进行方法调用等等的操作呢?而匿名内部类,显然从我们写的这些个代码中,完全不知道怎么加载这个类
这两个问题其实很简单,但是我在今天之前确实没有思考过,了解了以后,觉得 匿名内部类
这个名字挺好玩的。
解密
其实 Java 的这种问题还都挺好解决的,看它的字节码文件就好了。
我这里写了两个Java文件
ITest.java
public interface ITest {
void test();
}
Test.java
ITest t = new ITest(){
@Override
public void test() {
}
};
当我编译这两个文件之后,神奇的事情发生了,多了一个 class 文件, Test$1.class
没错,查看 Test$1.class 的反编文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
final class Test$1 implements ITest {
Test$1() {
}
public void test() {
}
}
查看 Test.class 的反编文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Test {
public Test() {
}
public static void main(String[] var0) {
ITest var10000 = new ITest() {
public void test() {
}
};
}
}
发现还是不太对,虽然创建了一个 Test$1 的类,但是 Test.java 并没有调用? 但是后来一想,这也是 class 文件反编译而来,可能存在一些问题,不如直接看字节码咯
Test.java 字节码
static INNERCLASS Test$1 null null
// access flags 0x1
public <init>()V
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 3 L0
NEW Test$1
DUP
INVOKESPECIAL Test$1.<init> ()V
ASTORE 1
L1
LINENUMBER 10 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
LOCALVARIABLE t LITest; L1 L2 1
MAXSTACK = 2
MAXLOCALS = 2
}
可以看到 main 函数,调用了 NEW Test$1 和 INVOKESPECIAL Test$1. ()V。
这里也就知道了,new 抽象类和接口并没有什么神奇的。只不过是在编译过程中,java 编译器,帮我们自动生成一些类,而这些类的名字就是,xxx$x
,名字也是编译器来定的,所以写代码的人无需给它命名,所以就取了个匿名内部类
的名字(我猜的,不过挺形象)?
所以,两个问题也解决了。
问题一:仁者见仁,在写法上看确实是 new 了,但是追其本源,还是实例化的类文件,看你自己怎么看吧,我觉得还是 不能说
new 接口的,
问题二:因为编译器帮我们生成了一个匿名类 xxx$x