反射的概念
反射的概念是由 Smith 在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。
換句話說,就是能夠得到代碼自身的特征。
換句話說,就是把類本身也看成是對象,包括類中的變量名、方法名、內部類、超類、包、修飾符等等,都可以通過代碼來得到并被看成是對象。
java為此設計了一些類來方便我們使用反射。這些類并不多,它們分別是:Field、Constructor、Method、Class、Object,下面對這些類做一個簡單的說明。
摘抄于其它資料,僅供閱讀
Field 類:提供有關類或接口的屬性的信息,以及對它的動態訪問權限。反射的字段可能是一個類(靜態)屬性或實例屬性,簡單的理解可以把它看成一個封裝反射類的屬性的類。
Constructor 類:提供關于類的單個構造方法的信息以及對它的訪問權限。這個類和 Field 類不同,Field 類封裝了反射類的屬性,而 Constructor 類則封裝了反射類的構造方法。
Method 類:提供關于類或接口上某個單獨方法的信息。所反映的方法可能是類方法或實例方法(包括抽象方法)。 這個類不難理解,它是用來封裝反射類方法的一個類。
Class 類:類的實例表示正在運行的 Java 應用程序中的類和接口。枚舉是一種類,注釋是一種接口。每個數組屬于被映射為 Class 對象的一個類,所有具有相同元素類型和維數的數組都共享該 Class 對象。
Object 類:每個類都使用 Object 作為超類。所有對象(包括數組)都實現這個類的方法。
獲取Class類
有一個類,類名是Class,(首字母大寫,不同于關鍵字class)。任何一個java類都是這個Class類的對象,即“類本身也是對象”的感覺。
一旦我們獲取到了一個類的Class實例,那么在此基礎上要獲取Field、Constructor、Method等等的話也就很容易了(因此java的所有代碼都在類中的嘛)。所以首要步驟是獲取Class實例。
獲取類自身有三種方式:
(1)利用 對象.getClass() 的方式獲取該對象的Class實例;
(2)利用 對象.class 的方式來獲取Class實例,對于基本數據類型的封裝類,還可以采用.TYPE來獲取相對應的基本數據類型的Class實例;
(3)使用 Class類的靜態方法forName(“全路徑名”),用類的名字獲取一個Class實例。
示例
class ClassTest {
public static void main(String[] args) throws Exception {
String str1 = "abc";
Class cls1 = str1.getClass();//法一
Class cls2 = String.class;//法二
Class cls3 = Class.forName("java.lang.String");//法三
System.out.println(cls1 == cls2);
System.out.println(cls1 == cls3);
}
}
運行結果為
true
true
解釋
1、運行結果為true說明虛擬機為某個類只會產生一份字節碼,將來用這份字節碼可以產生多個實例對象。
2、也即是說,在運行期間,如果我們要產生某個類的對象,Java虛擬機(JVM)會檢查該類型的Class對象是否已被加載。如果沒有被加載,JVM會根據類的名稱找到.class文件并加載它。一旦某個類型的Class對象已被加載到內存,就可以用它來產生該類型的所有對象。
利用Class實例創建對象
以前我們創建對象都是用“new 類名()”的方式,現在我們先得到構造方法,并用構造方法來創建。現在我們要使用Consturctor(構造器)類:它代表某個類中的一個構造方法。
得到某個類所有的構造方法
Constructor [] constructors = Class.forName("java.lang.String").getConstructors();
得到某一個構造方法
Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
注:參數是一個Class實例,即去拿匹配這樣參數的構造方法。
創建實例對象,用Constructor的newInstance方法
傳統方式:String str=new String(new StringBuffer("abc"));
反射方式:String str=(String) constructor.newInstance(new StringBuffer("abc"));
注:newInstance()方法參數可變長,請嘗試放多個參數。不合適時,報異常IllegalArgumentException。
上述原理可以下面示例來演練
class Test {
public static void main(String[] args) throws Exception {
Class c = Class.forName("java.lang.String");
Constructor constructor = c.getConstructor(StringBuffer.class);
String str = (String) constructor.newInstance(new StringBuffer("abc"));
System.out.println(str);
}
}
利用Constructor來創建實例與利用Class類來創建實例
class類也有創建實例的方法,下面的例子進行了展示。
此例來源于//seahb.iteye.com/blog/855107。
import java.lang.reflect.Constructor;
class A {
private A() { // 將private改為public試試
System.out.println("A's constructor A() is called.");
}
private A(int a, int b) {
System.out.println("A's constructor A(a,b) is called.");
System.out.println("a:" + a + " b:" + b);
}
}
class B {
public static void main(String[] args) {
B b = new B();
System.out.println("通過Class.NewInstance()調用私有構造函數:");
b.byClassNewInstance();
System.out.println("通過Constructor.newInstance()調用私有構造函數:");
b.byConstructorNewInstance();
}
/* 法一:通過Class.NewInstance()創建新的類示例 */
private void byClassNewInstance() {
try {
Class c = Class.forName("A");
A a = (A) c.newInstance();//調用無參構造方法。如果方法是私有的,則運行時會異常IllegalAccessException
} catch (Exception e) {
e.printStackTrace();
System.out.println("通過Class.NewInstance()調用構造方法【失敗】");
}
}
/*法二:通過Constructor.newInstance()創建新的類示例 */
private void byConstructorNewInstance() {
try {
Class c = Class.forName("A");
Constructor c0 = c.getDeclaredConstructor();/* 調用無參構造方法 */
c0.setAccessible(true); //必須設置一下可見性后就可調用了
A a0 = (A) c0.newInstance();//調用構造方法
System.out.println("成功1");
Constructor c1 = c.getDeclaredConstructor(new Class[] { int.class, int.class });/* 調用帶參構造方法 */
c1.setAccessible(true);
//A a1 = (A) c1.newInstance(new Object[] { 5, 6 });//參數是對象數組
A a1 = (A) c1.newInstance(5, 6);//參數可連寫,因為newInstance()支持可變參數
//A a1 = (A) c1.newInstance(5, 6,7);//參數若不合適,則就報異常IllegalArgumentException
System.out.println("成功2");
} catch (Exception e) {
e.printStackTrace();
}
}
}
結論
class.newInstance和constructor.newInstance 區別
通過反射創建新的類示例,有兩種方式:
Class.newInstance()
Constructor.newInstance()
Class.newInstance() 只能夠調用無參的構造函數,即默認的構造函數;
Constructor.newInstance() 可以根據傳入的參數,調用任意構造構造函數。
Class.newInstance() 要求被調用的構造函數是可見的,也即必須是public類型的;
Constructor.newInstance() 在特定的情況下,可以調用私有的構造函數。
如果被調用的類的構造函數為默認的構造函數,采用Class.newInstance()則是比較好的選擇,一句代碼就OK;如果是調用帶參構造函數、私有構造函數,就需要采用Constractor.newInstance(),兩種情況視使用情況而定。不過Java Totorial中推薦采用Constractor.newInstance()。