黑馬程式設計師—JAVA高新技術之反射
反射
一、反射的基石
反射的基石是Class類。Java程式中的各個java類屬於同一類事物,描述這類事物的java類名就是Class.
1.1 Class類的分析
在程式執行時呼叫類的時候,首先將這個類在硬碟上的二進位制程式碼載入到記憶體中,才可以用這個類建立物件,也就是說先將類的位元組碼載入到記憶體,再用這個位元組碼建立物件。每個類的位元組碼就是一個Class類的例項物件。
1.2得到位元組碼對應的例項物件(Class型別)有三種方式:
1. 類名.class 例:PersonDemo.class
2. 物件.getClass() 例:new Person().getClass();
3. Class.forName(“完整類名”),
例:Class.forName(“java.util.Data”);
Class.forName方法的作用是返回位元組碼,返回方式有兩種:
第一種,如果位元組碼已經被載入進記憶體,虛擬機器快取了,那麼就可以直接用全類名的方式得到位元組碼Class.forName(String className)。
第二種,如果class檔案沒有被載加過,也就是說虛擬機器裡還沒有該位元組碼,就要指定檔名、載入器,來得到位元組碼(用載入器載入,把那份位元組碼快取起來,通過該方法返回位元組碼)Class.forName(String name, boolean initialize,
1.3九個預定義的Class例項物件
任何型別都是Class的例項物件。
八個基本資料型別都是Class類的例項物件,void類也是Class類的例項物件,Class cls = void.class;
方法isPrimitive()用於判斷指定的Class物件是否表示一個基本型別(原始型別),有八個基本型別和關鍵字void。基本型別和void的Class例項物件都可用對應的包裝型別的常量值TYPE來獲取。
例:Integer.TYPE表示所包裝的基本型別int的Class例項物件。
Class類中有個方法isArray()用於判斷Class例項是不是陣列型別。
總之,只要是在源程式中出現的型別,都有各自的Class例項物件。
二、反射的概念
反射就是把java類中的各種成分對映成相應的java類,例如:一個java類中用一個Class類的物件來表示,一個類中的組成部分:成員變數,方法,構造方法,包等等資訊也用一個個的java類來表示,就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。
表示java類的Class類顯要提供一系列的方法,來獲得其中的變數,方法,構造方法,修飾符,包等資訊,這些資訊就是用相應類的例項物件來表示。它們是Field、Method、Contructor、Package等等。
一個類中的每個成員都可以用相應的反射API類的一個例項物件來表示,通過呼叫Class類的方法可以得到這些例項物件。
三、 Constructor類
Constructor類代表某個類中的一個構造方法。
import java.lang.reflect.*;
public class ConstructorTest {
public static void main(String[] args)throws Exception {
// 返回類裡面的所有構造方法
Constructor[] constructors = String.class.getConstructors();
//用引數型別來指定某一個構造方法,從而返回該構造方法物件
Constructor con = String.class.getConstructor(StringBuilder.class);
//用得到的有參構造方法進行new物件,用的什麼引數型別獲取的構造方法,
//在new物件的時候,就一定要用相應的實際引數型別傳值。
String str = (String)con.newInstance(new StringBuilder("abc"));//該方法返回的是Object所以一定要強轉
System.out.println(str.charAt(2));
//這個方法也是用於建物件,只是通過無參構造方法建的。
String.class.newInstance();
}
}
Class類裡面的newInstance()方法就是在用無參的構造方法建立一個物件。
四、Field類
Field類代表某個類中的一個成員變數,也就是位元組碼裡的一個變數,不代表一個物件上的變數。所以用get()方法獲取變數值時要指定是哪個物件。
4.1成員變數的反射
當成員變數私有化時,通過Class物件呼叫getDeclaredField方法傳入指定的成員變數字串,返回Field物件,並通過Field物件呼叫setAccessible(true)方法設為可訪問性,就可用get方法獲取到私有的成員變數,也稱暴力反射。
public class ReflectPoint {
public int x;
private int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
import java.lang.reflect.*;
public class ReflectDemo
{
public static void main(String[] rags)throws Exception{
ReflectPoint rp = new ReflectPoint(5,9);
Field fieldX = rp.getClass().getField("x");
System.out.println(fieldX.get(rp));
Field fieldY = rp.getClass().getDeclaredField("y");
fieldY.setAccessible(true);
System.out.println(fieldY.get(rp));
}
}
4.2獲取一個類中所有String型別的成員變數,並替換指定字元
import java.lang.reflect.*;
public class ReflexDemo {
public static void main(String[] args) throws Exception {
Student s = new Student("zhangsan","beijing",23);
refField(s);
System.out.println(s.name+":"+s.add+":"+s.age);
}
static void refField(Object o) throws Exception{
Field[] field = o.getClass().getFields();
for(Field f : field){
if(f.getType() == String.class){
String oldValue = (String)f.get(o);
String newValue = oldValue.replaceAll("zhangsan", "lisi");
f.set(o, newValue);
}
}
}
}
class Student{
public String name,add;
public int age;
public Student(String name,String add,int age){
this.name = name;
this.add = add;
this.age = age;
}
}
五、Method類
Method類代表某個類中的一個成員方法。
5.1成員方法的反射
1、得到成員方法物件:Class物件.getMethod(“方法名”,引數型別.class);
方法有幾個引數型別就要寫幾個對應的.class。
2、用反射方式呼叫方法:Method物件.invoke(物件,實際引數);
如果傳遞給Method物件的invoke()方法的第一個引數為null,該Method物件對應的是一個靜態方法,因為靜態方法不需要用物件呼叫,是跟著類走的。
3、JDK1.4和JDK1.5的invoke方法的區別:
JDK1.5:public Object invoke(Object obj,Object。。。args)支援可變引數
JDK1.4:public Object invoke(Object obj,Object[] args)需要將一個數組作為引數傳遞給invoke方法時,陣列中的每個元素分別對應被呼叫方法中的一個引數,所以,用JDK1.4寫為charAt.invoke(“str”,new Object[]{1})
例:
import java.lang.reflect.*;
public class ReflectDemo{
public static void main(String[] rags)throws Exception{
String str = new String("abcde");
Method method = String.class.getMethod("charAt", int.class);//得到method物件
System.out.println(method.invoke(str, 2));
}
}
5.2用反射方式執行某個類中的main方法
需求:寫一個程式,這個程式能夠根據使用者提供的類名,去執行該類中的main方法。
分析:啟動java程式的main方法的引數是一個字串陣列,即public static void main(String[] args),通過反射方式來呼叫這個main方法時,怎樣給invoke方法傳遞引數呢?按JDK1.5的語法,整個陣列是一個引數,而按1.4的語法,陣列中的每個元素對應一個引數,當把一個字串陣列作為引數傳遞給invoke方法時,javac會按1.4的語法處理(因為新版本要相容老版本),即把陣列打散成為若干個單獨的引數,所以給main方法傳遞引數時,不能寫mainMethod.invoke(null,new String[]{“abc”,”def”});
解決辦法有兩種:
1、mainMethod.invoke(null,new Object[]{new String[]{“xxx”}});這種方式相當於Object數組裡存了一個String陣列型別的元素,這樣編譯器就會認為是一個引數。
2、mainMethod.invoke(null.(Object)new String[]{“xxx”});這種方式編譯時,相當於是一個Object物件,編譯器就不會把引數當作陣列看待,也就不會把數組裡的元素打散成若干個引數。
具體程式碼如下:
import java.lang.reflect.*;
public class ReflectDemo{
public static void main(String[] args)throws Exception{
String startClassName = args[0];
Method mainMethod = Class.forName(startClassName).getMethod("main", String[].class);
mainMethod.invoke(null, (Object)new String[]{"123"});
}
}
class TestArguments{
public static void main(String[] args){
for(String str:args){
System.out.println(str);
}
}
}
注:要在執行ReflectTest的時候給主函式傳值,開啟Run Configurations視窗,並在Arguments選項卡的Program arguments欄裡輸入cn.itcast.day1.TestArguments 表示TestArguments位元組碼傳給ReflectTestr 的main函式,那麼在執行ReflectTest的時候就會同時載入得到TestArguments位元組碼檔案,就相當於。ReflectTest主函式的args[0],那麼就可通過Class.forName方法得到傳遞進來的位元組碼檔案物件。
六、陣列的反射
6.1陣列與Object的關係及其反射型別
1、只要陣列型別相同,維度相同,就是同一份位元組碼物件。
2、只要是引用資料型別(比如陣列型別)的超類都是Object,所以可以通過getSuperclass方法獲取到超類的Class物件。
3、如果陣列是一維的,數組裡的元素型別又是基本資料型別,那麼就不能寫成Object[] obj = a1;只能寫成Object obj = a1;
例:
int[] a1 = new int[3];
int[] a2 = new int[5];
int[] [] a3 = new int[3];
Stirng[] a4 = new String[3];
System.out.println(a1.getClass()==a2.getClass());//結果為true
System.out.println(a1.getClass().getSuperclass().getName());//結果為Object
Object a1Obj = a1;//正確
Object[] a1Obj = a1;//錯誤
Object[] a3Obj = a3;//正確,一維陣列是陣列型別,屬於Object子類,Object[]相當於有一個Object陣列,裡面存的是Object
Objcet[] a4Obj = a4;//正確,因為String是類型別,屬於Object子類
4、Arrays.asList()方法處理int[]和String[]時的差異:
從反射的角度理解:虛擬機器執行時會先根據JDK1.4的方法處理Arrays.asList(Object[] a),接收的是Object型別的陣列,當傳入的是String[],就代表String數組裡的每個元素都是一個引數並存到集合裡,所以可以全部打印出元素。這時int[]傳進來它代表的一個Object而不是Object[],不符合1.4的引數型別就處理不了,就會按1.5的方法處理,Arrays.asList(T…a)是接收的Object物件,就把int[]當作一個整體處理了,就相當於是一個引數,所以列印的是地址值。
個人理解:Arrays.asList()返回的是一個list集合,而集合裡只能存物件的引用,不能存基本資料型別int,所以列印list就是一個地址值,而String[]裡存的是String型別的元素,每個元素都所屬Object子類,列印list就可看到每個元素的值。
6.2、陣列的反射應用
Array類是用於對陣列反射的類。
例:用反射方式做:只要傳進來的是陣列就列印裡面的元素,不是陣列就直接列印物件。
public class ReflectArray{
public static void main(String[] args)throws Exception{
String[] str = new String[]{"ab","cde","w"};
Object obj = null;
printObject(str);
printObject("sss");
}
public static void printObject(Object obj){
if(obj.getClass().isArray()){
int len = Array.getLength(obj);
for(int x=0;x<len;x++){
System.out.println(Array.get(obj, x));
}
}
else{
System.out.println(obj);
}
}
}