黑馬程式設計師——Java高新技術_反射
反射技術:
其實就是動態載入一個指定的類,並獲取該類中的所有的內容。而且將位元組碼檔案封裝成物件,並將位元組碼檔案中的內容都封裝成物件,這樣便於操作這些成員。簡單說:反射技術可以對一個類進行解剖。
反射的好處:大大的增強了程式的擴充套件性。
反射的基本步驟:
1、獲得Class物件,就是獲取到指定的名稱的位元組碼檔案物件。
2、例項化物件,獲得類的屬性、方法或建構函式。
3、訪問屬性、呼叫方法、呼叫建構函式建立物件。
要想獲取位元組碼檔案中的成員,必須要先獲取位元組碼檔案物件
獲取位元組碼檔案物件的方式:
1、通過Object類中的getClass方法
雖然通過,但是前提必須有指定類,並對該類進行物件的建立,才可以通過呼叫getClass
Class clazz=new Person().getClass();//Person是一個類名
2、使用的任意資料類的一個靜態成員class,所有的資料型別都具備的一個屬性
好處:不用new物件,但是還需要使用具體的類
Class clazz=Person.class;//Person是一個類名
3、使用Class類中的forName方法,通過給定類名來獲取對應的位元組碼檔案物件
指定什麼類名,就獲取什麼類位元組碼檔案物件,這種方式的擴充套件性最強,只要將類名的字串傳入即可,獲取對應的位元組碼檔案直接由forName方法自動完成
Class clazz=Class.forName("包名.Person");//Person是一個類名
Class類中的方法
static Class forName(String className)
返回與給定字串名的類或介面的相關聯的Class物件。
Class getClass()
返回的是Object執行時的類,即返回Class物件即位元組碼物件
Constructor getConstructor()
返回Constructor物件,它反映此Class物件所表示的類的指定公共構造方法。
Field getField(String name)
返回一個Field物件,它表示此Class物件所代表的類或介面的指定公共成員欄位。
Field[] getFields()
返回包含某些Field物件的陣列,表示所代表類中的成員欄位。
Method getMethod(String name,Class… parameterTypes)
返回一個Method物件,它表示的是此Class物件所代表的類的指定公共成員方法。
Method[] getMehtods()
返回一個包含某些Method物件的陣列,是所代表的的類中的公共成員方法。
String getName()
以String形式返回此Class物件所表示的實體名稱。
String getSuperclass()
返回此Class所表示的類的超類的名稱
boolean isArray()
判定此Class物件是否表示一個數組
boolean isPrimitive()
判斷指定的Class物件是否是一個基本型別。
T newInstance()
建立此Class物件所表示的類的一個新例項。
通過Class物件獲取類例項
通過檢視API我們知道,Class類是沒有構造方法的, 因此只能通過方法獲取類例項物件。之前我們用的已知類,建立物件的做法:
1)查詢並載入XX.class檔案進記憶體,並將該檔案封裝成Class物件。
2)再依據Class物件建立該類具體的例項。
3)呼叫建構函式對物件進行初始化。
如:Person p=new Person();
現在用Class物件來獲取類例項物件的做法:
1)查詢並載入指定名字的位元組碼檔案進記憶體,並被封裝成Class物件。
2)通過Class物件的newInstance方法建立該Class對應的類例項。
3)呼叫newInstance()方法會去使用該類的空引數建構函式進行初始化。
如:
String className="包名.Person";
Class clazz=Class.forName(className);
Object obj=clazz.newInstance();
反射的用法:
1)、需要獲得java類的各個組成部分,首先需要獲得類的Class物件,獲得Class物件的三種方式:
Class.forName(classname)用於做類載入
obj.getClass() 用於獲得物件的型別
類名.class 用於獲得指定的型別,傳參用
// 1. 根據給定的類名來獲得 用於類載入
String classname = "cn.itcast.reflect.Person";// 來自配置檔案
Class clazz = Class.forName(classname);// 此物件代表Person.class
// 2. 如果拿到了物件,不知道是什麼型別 用於獲得物件的型別
Object obj = new Person();
Class clazz1 = obj.getClass();// 獲得物件具體的型別
// 3. 如果是明確地獲得某個類的Class物件 主要用於傳參
Class clazz2 = Person.class;
2)、反射類的成員方法:
Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();
3)、反射類的建構函式:
Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)
4)、反射類的屬性:
Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);
獲取了位元組碼檔案物件後,最終都需要建立指定類的物件:
建立物件的兩種方式(其實就是物件在進行例項化時的初始化方式):
1,呼叫空引數的建構函式:使用了Class類中的newInstance()方法。
Object obj=clazz.newInstance();
2,呼叫帶引數的建構函式:先要獲取指定引數列表的建構函式物件,然後通過該建構函式的物件的newInstance(實際引數) 進行物件的初始化。
綜上所述,第二種方式,必須要先明確具體的建構函式的引數型別,不便於擴充套件。所以一般情況下,被反射的類,內部通常都會提供一個公有的空引數的建構函式。
// 如何生成獲取到位元組碼檔案物件的例項物件。
Class clazz = Class.forName("cn.itcast.bean.Person");//類載入
// 直接獲得指定的型別
clazz = Person.class;
// 根據物件獲得型別
Object obj = new Person("zhangsan", 19);
clazz = obj.getClass();
Object obj = clazz.newInstance();//該例項化物件的方法呼叫就是指定類中的空引數建構函式,給建立物件進行初始化。當指定類中沒有空引數建構函式時,該如何建立該類物件呢?請看method_2();
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//既然類中沒有空引數的建構函式,那麼只有獲取指定引數的建構函式,用該函式來進行例項化。
//獲取一個帶引數的構造器。
Constructor constructor = clazz.getConstructor(String.class,int.class);
//想要對物件進行初始化,使用構造器的方法newInstance();
Object obj = constructor.newInstance("zhagnsan",30);
//獲取所有構造器。
Constructor[] constructors = clazz.getConstructors();//只包含公共的
constructors = clazz.getDeclaredConstructors();//包含私有的
for(Constructor con : constructors) {
System.out.println(con);
}
}
反射指定類中的方法:
//獲取類中所有的方法。
public static void method_1() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method[] methods = clazz.getMethods();//獲取的是該類中的公有方法和父類中的公有方法。
methods = clazz.getDeclaredMethods();//獲取本類中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//獲取指定方法;
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//獲取指定名稱的方法。
Method method = clazz.getMethod("show", int.class,String.class);
//想要執行指定方法,當然是方法物件最清楚,為了讓方法執行,呼叫方法物件的invoke方法即可,但是方法執行必須要明確所屬的物件和具體的實際引數。
Object obj = clazz.newInstance();
method.invoke(obj, 39,"hehehe");//執行一個方法
}
//想要執行私有方法。
public static void method_3() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//想要獲取私有方法。必須用getDeclearMethod();
Method method = clazz.getDeclaredMethod("method", null);
// 私有方法不能直接訪問,因為許可權不夠。非要訪問,可以通過暴力的方式。
method.setAccessible(true);//一般很少用,因為私有就是隱藏起來,所以儘量不要訪問。
}
//反射靜態方法。
public static void method_4() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method method = clazz.getMethod("function",null);
method.invoke(null,null);
}
反射中常見的異常
ClassNotFoundException
InstantiationException//例項初始化異常,通常被反射的類都會有提供空引數的建構函式,沒有對應的建構函式就會出現此類
IllegalAccessException//如果有提供,但是沒有訪問許可權的話就會出現此異常
如何通過給定的建構函式例項化
如果要通過指定的建構函式初始化物件,步驟如下:
1,獲取位元組碼檔案物件
2,再獲取給定的建構函式
3,通過建構函式初始化物件
String className = “com.itheima.Person”;
Class clazz = Class.forName(className);
//獲取指定的構造器,獲取(指定類)Person中兩個引數String,int的建構函式
Constructor cons = clazz.getConstructor (String.class,int.class)
//有了構造器物件後,同構構造器物件來初始化該類物件
Object obj = cons.newInstance(“wangwu”,23);
//獲取欄位
Field field = clazz.getField(fieldName);//獲取收到是公共的欄位
Field field = clazz.getDeclaredField(fieldName);
//getXXX:獲取都是類中公共的成員
//getDeclaredXXX:獲取本類中已有的成員
//對其進行值的設定,必須先有物件
Object obj = clazz.newInstance();
//通過查詢父類AccessiableObject的方法,setAccessiable(true)
field.setAccessiable(true);//取消許可權檢查,暴力訪問。一般不訪問私有
field.set(obj,30)//若不在上面加暴力訪問將出現IllegalAccessException:age欄位是私有的