为什么Android的Void实现和JDK有区别?

软件工程师、主攻高级编程语言虚拟机的设计与实现

86 👍 / 7 💬

问题描述

public final
class Void {

    /**
     * The {@code Class} object representing the pseudo-type corresponding to
     * the keyword {@code void}.
     */
    public static final Class<Void> TYPE = lookupType();

    @SuppressWarnings("unchecked")
    private static Class<Void> lookupType() {
        try {
            Method method = Runnable.class.getMethod("run", EmptyArray.CLASS);
            return (Class<Void>) method.getReturnType();
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    /*
     * The Void class cannot be instantiated.
     */
    private Void() {}
}

Android中的Void class是这样的实现,而JDK中的Void是直接调用Class.getPrimitiveClass()。想问一下这样的设计是如何考虑的?Android中很多类都和JDK中的实现不同,例如EmptyArray这个类在很多地方都有出现。请问是出于什么目的呢?

从Android N开始,Android的Java标准库已经转成用OpenJDK了。于是从Android N开始题主就能看到Android用的也是题主所说的“JDK”版的样子了。

在Android N之前,Android的Java标准库用的是Apache Harmony所做的版本。

Java的标准兼容性测试只关心标准库中public API是否得到了忠实、完整的实现。而题主问的细节则是私有的实现细节,代码不一样是很正常的。

事实上Apache Harmony的开发有严格的指引,不可以去阅读OpenJDK的源码,也不可以有机会接触过从Sun(后来Oracle)通过许可证获得的Sun/Oracle JDK的源码。所以Apache Harmony里的实现跟OpenJDK里对应的具体不一样是非常正常、而且Harmony项目希望看到的事情——但他们没办法验证自己的代码跟OpenJDK是否长得一模一样,因为不能看人家的代码啊。

具体到题主给的例子:

(这里说的“void.class”指的是Java语言的表达式的“Class Literal”语法,不是名为void.class的Class文件)

不要小看了标准库里各部分之间的依赖关系。在很底层的地方,某个类A的初始化不小心依赖了另一个类B的话,如果在JVM内部还在“混沌”的初始化过程中,A一定要比B先初始化,那这整个JDK就启动不了…能发布出来的JDK代码在很底层都是经过细致调整来避开这些坑的。

但肯定会有同学问:既然我们可以通过“void.class”语法来访问到void对应的Class对象,为啥大家在这里都要这么绕弯,为啥不直接写成:

public class Void {
  public static final Class<Void> TYPE = void.class;

  private Void() { }
}

哈哈哈哈。

这是因为:Java语言层面上的“void.class”语法,跟一般的类/接口对应的Class不同,其实是包装类上的字段Void.TYPE的语法糖。

同理,int.class其实是Integer.TYPE的语法糖,long.class其实是Long.TYPE的语法糖,等等。

例如说,下面这段Java代码:

public class Test {
  public static void main(String[] args) {
    Class<Integer> cint = int.class;
    Class<Void> cvoid = void.class;
    System.out.printf("%s, %s\n", cint, cvoid);
  }
}

其实通过Java源码级编译器编译到Class文件后,其内容是等价于下面这样的:

public class Test {
  public static void main(String[] args) {
    Class<Integer> cint = Integer.TYPE;
    Class<Void> cvoid = Void.TYPE;
    System.out.printf("%s, %s\n", cint, cvoid);
  }
}

所以如果Void.TYPE用void.class来实现的话,

public class Void {
  public static final Class<Void> TYPE = void.class; // <--

  private Void() { }
}

这里就会变成 TYPE = Void.TYPE,递归了,就不能行了…

顺带放个相关传送门:

谁来解释下基本类型变量的class,如int.class.什么时候会使用它们? - RednaxelaFX 的回答 - 知乎