目录

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