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

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…
关注者
46
被浏览
8773
从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是否长得一模一样,因为不能看人家的代码啊。

具体到题主给的例子:
  • Android(Harmony)版的做法本质上是找一个well-known的返回类型为void的方法,然后通过常规的反射API去获取void.class对应的java.lang.Class对象。这里选用的是java.lang.Runnable.run(),返回void类型。这样的话,好处是不需要额外写任何native方法去实现这个功能,而坏处是使得Void类依赖于标准库的反射API的实现,所以其初始化时机必须在反射API完全初始化之后。
  • OpenJDK版的做法是,在java.lang.Class类里放个包可访问的native方法,getPrimitiveClass(),用来让标准库的代码直接问JVM要原始类型和void对应的Class对象的引用。这样坏处是得让标准库多实现一个native方法,并且通过与VM的私有接口来实现功能,具体到这个例子是JVM_FindPrimitiveClass()函数——JDK标准库的代码调用这个函数,而匹配的JVM则要实现这个函数;而好处是使得Void类不依赖于反射API的完整实现,其初始化时机可以更加灵活。
(这里说的“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 的回答 - 知乎