1. 程式人生 > >Java使用new和反射例項化內部類物件

Java使用new和反射例項化內部類物件

使用new和反射例項化內部類物件
前兩天看到一道面試題,是關於內部類的知識,覺得很有意思,現對其詳細講解,絕對原創!~
這道題是這樣的:
根據註釋填寫(1),(2),(3)處的程式碼
public class Test{
public static void main(String[] args){
// 初始化Bean1
(1)
bean1.I++;
// 初始化Bean2
(2)
bean2.J++;
//初始化Bean3
(3)
bean3.k++;
}
class Bean1{
public int I = 0;
}
static class Bean2{
public int J = 0;
}
}
public class Bean{
public class Bean3{


public int k = 0;
}
}
這其實就是例項化內部類物件的題。
從上面的題可以看出,Bean1為非靜態內部類,Bean2為靜態內部類,而Bean3則不是在Test類內部了,而是在一個外部類Bean的內部(是不是很拗口),呵呵。現通過new和反射來詳細講解其產生原理。
1.new
我們知道,內部類分為兩種,一種是靜態內部類,一種是非靜態內部類。前者不用產生外部類的例項化物件即可產生內部類的例項化物件,後者必須先產生外部類的例項化物件,才能產生內部類的例項化物件。
例項化靜態內部類物件的模板是: 外部類類名.內部類類名 xxx = new 外部類類名.內部類類名()
例項化非靜態內部類物件的模板是:外部類類名.內部類類名 xxx = 外部類物件名.new 內部類類名()

1>>例項化非靜態內部類Bean1
java程式碼 :
Test test = new Test();
Test.Bean1 b1 = test.new Bean1();
b1.I++;
總共3行程式碼,是不是很簡單呢。
2>>例項化靜態內部類Bean2
java程式碼:
Test.Bean2 b2 = new Test.Bean2();
b2.j++;
3>>例項化非靜態內部類Bean3
Bean bean = new Bean();
Bean.Bean3 b3 = bean.new Bean3();
System.out.println(b3.k++);
總結:通過new方式產生內部類的例項化物件實現起來比較簡單,也很容易理解,如果要深層次瞭解其產生,請用下面講解的方式,反射。

2.反射
1>>反射產生非靜態內部類Bean1的例項化物件
java程式碼:
try {
Class<?> cla2 = Class.forName("com.lovo.nio.Test$Bean1");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
解析:我們知道,內部類的class檔案是以"外部類$內部類.class"的形式存在的,所以獲取Class物件的時候,必須使用forName("包名+外部類$內部類")的形式才能得到Class物件
得到Class物件cla2後,肯定有人會說用下面的方法得到Bean1的例項化物件:
Bean1 b6 = (Bean1)cla2.newInstance();
執行上述程式碼,出現異常:InstantiationException,檢視Java API文件,下面引用其原話:
當應用程式試圖使用 Class 類中的 newInstance 方法建立一個類的例項,而指定的類物件無法被例項化時,丟擲該異常。例項化失敗有很多原因,包括但不僅限於以下原因:
類物件表示一個抽象類、介面、陣列類、基本型別、void
類沒有非 null 構造方法
在這裡的原因是:Bean1的構造方法不公開,意思就是說,他的構造方法不是public的,不能通過newInstance()方式產生他的例項化物件。
那麼我們是否可以檢視其是什麼訪問修飾符的構造方法呢?答案是可以的,可以通過以下方式得到:
Constructor<?>[] c = cla2.getDeclaredConstructors();
int i = c[0].getModifiers();
//得到訪問修飾符
String str = Modifier.toString(i);
System.out.println(str+" aaaaaaaaa"); //注意:此處的aaaaaaaaaaa僅表示有這個輸出
執行以上程式碼,輸出“ aaaaaaaaa”,可以看出並沒有輸出str,實際上已經輸出了,就是default,default在Java中可以省略不寫的。現在該明白了吧!~
那要如何才能例項化他的內部類物件呢,剛才我們說過,要例項化非靜態的內部類物件,必須先例項化外部類的物件,可是我們任然沒有例項化外部類的物件。我們檢視JAVA PAI文件,發現Constructor類有一個方法,(... initargs),於是我們想到下面這種方式來構建:
//反射產生非靜態內部類Bean1的例項化物件
try {
Class<?> cla2 = Class.forName("com.lovo.nio.Test$Bean1");
// Bean1 b6 = (Bean1)cla2.newInstance();
// System.out.println(b6);
Constructor<?>[] c = cla2.getDeclaredConstructors();
int i = c[0].getModifiers();
//得到訪問修飾符
String str = Modifier.toString(i);
System.out.println(str+" aaaaaaaaa");
Bean1 bean1 = (Bean1)c[0].newInstance(new Test());
System.out.println(bean1.I++);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
執行以上程式碼,正常。
2>>反射產生靜態內部類Bean2的例項化物件
Java程式碼:
//反射產生靜態內部類Bean2的例項化物件
try {
// 初始化Bean2 
Class<?> cla = null;
cla = Class.forName("com.lovo.nio.Test$Bean2");
Bean2 bean2 = (Bean2)cla.newInstance();
System.out.println(bean2.j++);
} catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
解析:
首先來看看static的相關知識:
static內部類意味著: 
(1) 為建立一個static內部類的物件,我們不需要一個外部類物件。 
(2) 不能從static內部類的一個物件中訪問一個外部類物件. 
倘若為了建立內部類的物件而不需要建立外部類的一個物件,那麼可將所有東西都設為static。為了能正常工作,同時也必須將內部類設為static。 
此外,也可考慮用一個static內部類容納自己的測試程式碼。
在這裡我們同樣可以使用上面的方法獲取他的構造方法的修飾符,也是default的,(靜態內部類是有構造方法的,而且是無參的構造方法).
java程式碼:
try {


// 初始化Bean2 
Class<?> cla = null;
cla = Class.forName("com.lovo.nio.Test$Bean2");
Bean2 bean2 = (Bean2)cla.newInstance();
Constructor<?>[] cs = cla.getDeclaredConstructors();
// Modifier.toString(cs[0]);
System.out.println("******************");
System.err.println(cs.length);
System.out.println(cs[0].getModifiers());
System.out.println("******************");
System.out.println(bean2.j++);
} catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
執行以上程式碼,紅色部分程式碼輸出:
******************
1
******************
可以看出,有一個預設的(方法修飾符沒寫的,不是沒有方法修飾符)構造方法。
如果我們要使用反射產生Bean2的例項化物件的話,只能用getDeclaredConstructors()方法。如上面的程式碼所示。
3>>反射產生 外部類的內部類Bean3 的例項化物件
分析:要產生外部類的內部類的例項化物件,則要先產生外部類的例項化物件。再通過getClass()方法得到外部類的例項化物件的Class物件,再通過getClasses()方法得到外部類的所有公共類和介面,包括內部類。
Java程式碼:
try {
Class<?> c3 = bean.getClass();
Class<?>[] cl = c3.getClasses();
// Bean3 b4 = (Bean3)c3.newInstance();
// System.out.println(b4);
//使用反射產生Bean3例項化物件
Constructor<?>[] cc = cl[0].getDeclaredConstructors();
//獲取構造方法的個數,用以判定其構造方法是否是公共的,如果直接通過c3.newInstance()方法來例項化Bean3的話,則會包異常:java.lang.ClassCastException
System.out.println(cc.length);
Bean3 bb = (Bean3)cc[0].newInstance(new Bean());
System.out.println(bb.k++);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}