下面這個(gè)程序通過打印一個(gè)由反射創(chuàng)建的對象來產(chǎn)生輸出。那么它會打印出什么呢?
public class Outer{
public static void main(String[] args) throws Exception{
new Outer().greetWorld();
}
private void greetWorld()throws Exception {
System.out.println( Inner.class.newInstance() );
}
public class Inner{
public String toString(){
return "Hello world";
}
}
}
這個(gè)程序看起來是最普通的Hello World程序的又一個(gè)特殊的變體。Outer中的main方法創(chuàng)建了一個(gè)Outer實(shí)例,并且調(diào)用了它的greetWorld方法,該方法以字符串形式打印了通過反射創(chuàng)建的一個(gè)新的Inner實(shí)例。Inner的toString方法總是返回標(biāo)準(zhǔn)的問候語,所以程序的輸出應(yīng)該與往常一樣,是Hello World。如果你嘗試運(yùn)行這個(gè)程序,你會發(fā)現(xiàn)實(shí)際的輸出比較長,而且更加令人迷惑:
Exception in thread "main" InstantiationException: Outer$Inner
at java.lang.Class.newInstance0(Class.java:335)
at java.lang.Class.newInstance(Class.java:303)
at Outer.greetWorld(Outer.java:7)
at Outer.main(Outer.java:3)
為什么會拋出這個(gè)異常呢?從5.0版本開始,關(guān)于Class.newInstance的文檔敘述道:如果那個(gè)Class對象“代表了一個(gè)抽象類(abstract class),一個(gè)接口(interface),一個(gè)數(shù)組類(array class),一個(gè)原始類型(primitive type),或者是空(void);或者這個(gè)類沒有任何空的[也就是無參數(shù)的]構(gòu)造器;或者實(shí)例化由于某些其他原因而失敗,那么它就會拋出異?!盵JAVA-API]。這里出現(xiàn)的問題滿足上面的哪些條件呢?遺憾的是,異常信息沒有提供任何提示。在這些條件中,只有后2個(gè)有可能會滿足:要么是Outer.Inner沒有空的構(gòu)造器,要么是實(shí)例化由于“某些其它原因”而失敗了。正如Outer.Inner這種情況,當(dāng)一個(gè)類沒有任何顯式的構(gòu)造器時(shí),Java會自動(dòng)地提供一個(gè)不帶參數(shù)的公共的缺省構(gòu)造器[JLS 8.8.9],所以它應(yīng)該是有一個(gè)空構(gòu)造器的。不過,newInstance方法調(diào)用失敗的原因還是因?yàn)镺uter.Inner沒有空構(gòu)造器!
一個(gè)非靜態(tài)的嵌套類的構(gòu)造器,在編譯的時(shí)候會將一個(gè)隱藏的參數(shù)作為它的第一個(gè)參數(shù),這個(gè)參數(shù)表示了它的直接外圍實(shí)例(immediately enclosing instance)[JLS 13.1]。當(dāng)你在代碼中任何可以讓編譯器找到合適的外圍實(shí)例的地方去調(diào)用構(gòu)造器的時(shí)候,這個(gè)參數(shù)就會被隱式地傳遞進(jìn)去。但是,上述的過程只適用于普通的構(gòu)造器調(diào)用,也就是不使用反射的情況。當(dāng)你使用反射調(diào)用構(gòu)造器時(shí),這個(gè)隱藏的參數(shù)就需要被顯式地傳遞,這對于Class.newInstance方法是不可能做到的。要傳遞這個(gè)隱藏參數(shù)的辦法就是使用java.lang.reflect.Constructor。當(dāng)對程序進(jìn)行了這樣的修改后,它就可以正常的打印出 Hello World了:
private void greetWorld() throws Exception{
Constructor c = Inner.class.getConstructor(Outer.class);
System.out.println(c.newInstance(Outer.this));
}
作為其他的選擇,你可能觀察到了,Inner實(shí)例并不需要一個(gè)外圍的Outer實(shí)例,所以可以將Inner類型聲明為靜態(tài)的(static)。除非你確實(shí)是需要一個(gè)外圍實(shí)例,否則你應(yīng)該優(yōu)先使用靜態(tài)成員類(static member class)而不是非靜態(tài)成員類[EJ Item 18]。下面這個(gè)簡單的修改就可以訂正這個(gè)程序:
public static class Inner{...}
Java程序的反射模型和它的語言模型是不同的。反射操作處于虛擬機(jī)層次,暴露了很多從Java程序到class文件的翻譯細(xì)節(jié)。這些細(xì)節(jié)當(dāng)中的一部分由Java的語言規(guī)范來管理,但是其余的部分可能會隨著不同的具體實(shí)現(xiàn)而有所不同。在Java語言的早期版本中,從Java程序到class文件的映射是很直接的,但是隨著一些不能被虛擬機(jī)直接支持的高級語言特性的加入,如嵌套類(nested class)、協(xié)變返回類型(covariant return types)、泛型(generics)和枚舉類型(enums),使得這種映射變得越來越復(fù)雜了。
考慮到從Java程序到class文件的映射的復(fù)雜度,請避免使用反射來實(shí)例化內(nèi)部類。更一般地講,當(dāng)我們在用高級語言特性定義的程序元素之上使用反射的時(shí)候,一定要小心,從反射的視角觀察程序可能不同與從代碼的視角去觀察它。請避免依賴那些沒有被語言規(guī)范所管理的翻譯細(xì)節(jié)。對于平臺的實(shí)現(xiàn)者來說,這里的教訓(xùn)就是要再次重申,請?zhí)峁┣逦鷾?zhǔn)確的診斷信息。
public class Outer{
public static void main(String[] args) throws Exception{
new Outer().greetWorld();
}
private void greetWorld()throws Exception {
System.out.println( Inner.class.newInstance() );
}
public class Inner{
public String toString(){
return "Hello world";
}
}
}
這個(gè)程序看起來是最普通的Hello World程序的又一個(gè)特殊的變體。Outer中的main方法創(chuàng)建了一個(gè)Outer實(shí)例,并且調(diào)用了它的greetWorld方法,該方法以字符串形式打印了通過反射創(chuàng)建的一個(gè)新的Inner實(shí)例。Inner的toString方法總是返回標(biāo)準(zhǔn)的問候語,所以程序的輸出應(yīng)該與往常一樣,是Hello World。如果你嘗試運(yùn)行這個(gè)程序,你會發(fā)現(xiàn)實(shí)際的輸出比較長,而且更加令人迷惑:
Exception in thread "main" InstantiationException: Outer$Inner
at java.lang.Class.newInstance0(Class.java:335)
at java.lang.Class.newInstance(Class.java:303)
at Outer.greetWorld(Outer.java:7)
at Outer.main(Outer.java:3)
為什么會拋出這個(gè)異常呢?從5.0版本開始,關(guān)于Class.newInstance的文檔敘述道:如果那個(gè)Class對象“代表了一個(gè)抽象類(abstract class),一個(gè)接口(interface),一個(gè)數(shù)組類(array class),一個(gè)原始類型(primitive type),或者是空(void);或者這個(gè)類沒有任何空的[也就是無參數(shù)的]構(gòu)造器;或者實(shí)例化由于某些其他原因而失敗,那么它就會拋出異?!盵JAVA-API]。這里出現(xiàn)的問題滿足上面的哪些條件呢?遺憾的是,異常信息沒有提供任何提示。在這些條件中,只有后2個(gè)有可能會滿足:要么是Outer.Inner沒有空的構(gòu)造器,要么是實(shí)例化由于“某些其它原因”而失敗了。正如Outer.Inner這種情況,當(dāng)一個(gè)類沒有任何顯式的構(gòu)造器時(shí),Java會自動(dòng)地提供一個(gè)不帶參數(shù)的公共的缺省構(gòu)造器[JLS 8.8.9],所以它應(yīng)該是有一個(gè)空構(gòu)造器的。不過,newInstance方法調(diào)用失敗的原因還是因?yàn)镺uter.Inner沒有空構(gòu)造器!
一個(gè)非靜態(tài)的嵌套類的構(gòu)造器,在編譯的時(shí)候會將一個(gè)隱藏的參數(shù)作為它的第一個(gè)參數(shù),這個(gè)參數(shù)表示了它的直接外圍實(shí)例(immediately enclosing instance)[JLS 13.1]。當(dāng)你在代碼中任何可以讓編譯器找到合適的外圍實(shí)例的地方去調(diào)用構(gòu)造器的時(shí)候,這個(gè)參數(shù)就會被隱式地傳遞進(jìn)去。但是,上述的過程只適用于普通的構(gòu)造器調(diào)用,也就是不使用反射的情況。當(dāng)你使用反射調(diào)用構(gòu)造器時(shí),這個(gè)隱藏的參數(shù)就需要被顯式地傳遞,這對于Class.newInstance方法是不可能做到的。要傳遞這個(gè)隱藏參數(shù)的辦法就是使用java.lang.reflect.Constructor。當(dāng)對程序進(jìn)行了這樣的修改后,它就可以正常的打印出 Hello World了:
private void greetWorld() throws Exception{
Constructor c = Inner.class.getConstructor(Outer.class);
System.out.println(c.newInstance(Outer.this));
}
作為其他的選擇,你可能觀察到了,Inner實(shí)例并不需要一個(gè)外圍的Outer實(shí)例,所以可以將Inner類型聲明為靜態(tài)的(static)。除非你確實(shí)是需要一個(gè)外圍實(shí)例,否則你應(yīng)該優(yōu)先使用靜態(tài)成員類(static member class)而不是非靜態(tài)成員類[EJ Item 18]。下面這個(gè)簡單的修改就可以訂正這個(gè)程序:
public static class Inner{...}
Java程序的反射模型和它的語言模型是不同的。反射操作處于虛擬機(jī)層次,暴露了很多從Java程序到class文件的翻譯細(xì)節(jié)。這些細(xì)節(jié)當(dāng)中的一部分由Java的語言規(guī)范來管理,但是其余的部分可能會隨著不同的具體實(shí)現(xiàn)而有所不同。在Java語言的早期版本中,從Java程序到class文件的映射是很直接的,但是隨著一些不能被虛擬機(jī)直接支持的高級語言特性的加入,如嵌套類(nested class)、協(xié)變返回類型(covariant return types)、泛型(generics)和枚舉類型(enums),使得這種映射變得越來越復(fù)雜了。
考慮到從Java程序到class文件的映射的復(fù)雜度,請避免使用反射來實(shí)例化內(nèi)部類。更一般地講,當(dāng)我們在用高級語言特性定義的程序元素之上使用反射的時(shí)候,一定要小心,從反射的視角觀察程序可能不同與從代碼的視角去觀察它。請避免依賴那些沒有被語言規(guī)范所管理的翻譯細(xì)節(jié)。對于平臺的實(shí)現(xiàn)者來說,這里的教訓(xùn)就是要再次重申,請?zhí)峁┣逦鷾?zhǔn)確的診斷信息。