為什麼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& TYPE = lookupType();

@SuppressWarnings("unchecked")
private static Class& lookupType() {
try {
Method method = Runnable.class.getMethod("run", EmptyArray.CLASS);
return (Class&) 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是否長得一模一樣,因為不能看人家的代碼啊。

具體到題主給的例子:

  • 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& 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& cint = int.class;
Class& cvoid = void.class;
System.out.printf("%s, %s
", cint, cvoid);
}
}

其實通過Java源碼級編譯器編譯到Class文件後,其內容是等價於下面這樣的:

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

所以如果Void.TYPE用void.class來實現的話,

public class Void {
public static final Class& TYPE = void.class; // &<-- private Void() { } }

這裡就會變成 TYPE = Void.TYPE,遞歸了,就不能行了…

順帶放個相關傳送門:誰來解釋下基本類型變數的class,如int.class.什麼時候會使用它們? - RednaxelaFX 的回答 - 知乎


歪個題,具體的R大已經解釋了。其實不光Void的實現,Android之前用的Harmony庫中常用的集合類有些細節和openJDK也是不一樣的:比如Android之前ArrayList的DEFAULT_CAPACITY是12,而OpenJDK/Oracle中的是10,包括String類的內部實現。

不過這些細枝末節對api以及效率的影響算是微乎其微,現在的源碼也都換成了OpenJDK


推薦閱讀:

Android程序員技術等級標準?
小米的miui能否解決安卓的SD卡文件夾「碎片化」?
你遇到過哪些代碼優雅的安卓項目?
為什麼 Android 版手機 QQ 不遵循 Android Design?

TAG:Android開發 | Java | Java虛擬機JVM | Android |