黑馬程序員_Java中的反射

---------------------- android培訓java培訓、期待與您交流! ----------------------

一、什麼是反射?

反射就是,將Java類中的各成分映射成相應的Java類(下面稱 映射類)。如下:
包--->Package
構造方法--->Constructor
普通方法--->Method
成員變量--->Field
類和成員修飾符--->Modifier
那麼,如何得到這些Java類? 從java文件的抽取類Class中獲取。要先獲取class的實例
二、反射的基石-->Class
Class用來描述內存中的字節碼文件。一個Class實例就是一個字節碼文件,同一類型的數據在內存中只有一份字節碼文件。
在內存中,也是通過該類的字節碼來創建對象的。
  字節碼文件:就是一個類被加載到內存之後在內存中的文件。
  1.如何理解Class?
  Java類用於描述一類事物的共性,該類事物有什麼屬性,沒有什麼屬性。屬性的值,則是由類的實例對象確定的,不同的實例對象屬性值不同。
Java程序中的各個java類文件(.class文件或字節碼文件),它們也是同一類事物,在Java中也對這類事物用一個Java類來描述,這個類的名字是Class。
簡單地說就是,Class是內存中從各個字節碼文件抽取出來的類,Class用來描述這些字節碼文件,字節碼文件是Class類的實例。
這類事物有什麼屬性?一個類的屬性有:類的名稱、屬於哪個包、實現的接口、類的成員變量、構造方法、普通方法等。
2.Class的實例對象:只要是在源程序中出現的類型,都有各自的Class實例對象。int[],Person,void都是。
2.1獲得Class實例對象的三個方法:
1.類名.class-->System.class
2.對象.getClass()-->new Person().getClass();
3.Class.forName("類名")-->Class.forName("java.lang.String");
面試題 forName()的作用?
用來得到一個類的字節碼。有兩種方法:
1.這個類的字節碼已經加載到內存中,直接獲取。
2.內存中還沒有加載,先從硬盤中找到該類字節碼,用類加載器加載到內存中緩衝起來,
下次使用時就不用加載了,直接獲取即可。   這說明反射會導致程序性能下降(1)
2.2三種Class實例對象
1. 9個預定義Class實例對象:8個基本類型、1個void
這9個要獲得字節碼文件只能用一種方法:基本類型.class
int.class==Integer.TYPE;獲取Integer中封裝的基本數據類型。
Class.isPrimitive();是否爲原始的基本數據類型。int[]不是
System.out.println(void.class);//void。void也是一種類型
2. 數組類型的Class實例對象:
數組要獲得字節碼文件也只能用一種方法:數組類型[].class
public boolean isArray();//int[].isArray();true
3.各種類的Class實例對象
3.映射類
  3.1Constructor類:代表某個類中的一個構造方法。即,Constructor類描述了所有類的所有構造方法。
  獲取一個類中的構造函數:獲取該類字節碼文件————>獲取指定構造方法
  1.獲取某一個構造方法:
  Constructor<T> getConstructor(Class<?>... parameterTypes):parameterTypes是類的字節碼文件
  Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):獲取私有構造方法
例如:獲取String中構造方法String(StringBuffer sb){}:
  Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);
  2.獲取所有構造方法:
Constructor<?>[] getConstructors()
Constructor<?>[] getDeclaredConstructors() 
例如:Constructor[] constructors=String.class.getConstructors();
  或者Constructor[] constructors=Class.forName("java.lang.String").getConstructors();
創建對象: 
注意:如果 Class 表示一個抽象類、接口、數組、基本類型或 void,則不能使用newInstance()方法創建對象。
   抽象類能獲取空參數的構造方法,接口中就沒有該方法也就無所謂獲取了。
  1.用已獲取的構造方法創建對象,無參時傳入null
   Constructor類中 -->public T newInstance();//返回的是任意對象,所以使用時要記得類型轉換
 普通方法創建String對象:有參:new String(new StringBuffered("abc"));無參:new String();
 反射方法創建String對象:有參:(String)String.class.getConstructor(StringBuffered.class).newInstance(new StringBuffered("abc"));
   無參:(String)String.class.getConstructor(null).newInstance(null);
  2.Class.newInstance()方法:調用默認構造函數創建實例
   Class類中 -->getConstructors(); newInstance();
在調用Class的該方法時,該方法內部先得到了默認的構造方法,用緩存機制將該構造方法緩衝起來,然後用該構造方法創建實例對象。
以後再次調用newInstance()方法時,不用在得到構造方法了,直接從緩存中起上用即可。 這說明反射會導致程序性能下降(2)
String str=Class.forName("java.lang.String").newInstance();
 
3.2 Field類:代表某個字節碼中的變量,而不是對象中的某個變量,所以是沒有值的。可以通過方法獲取值。
  獲取一個類中的某個變量:Class類的方法
  Field getField(String name) ;//獲取一個 非私有成員變量。name是成員變量名
Field[] getFields();獲取所有 非私有成員變量。 
Field getDeclaredField(String name) ;//只能查看私有變量,知道有這個成員,並不能使用.要想使用,暴力獲取
Field[] getDeclaredFields()://獲取 所有成員變量   
使用字節碼變量:Field的方法
setAccessible(true);暴力獲取私有變量,可以使用了
get(obj);獲取對象obj中的變量的值。
3.3 Method類:
獲取一個類中的某個方法:Class類的方法
Method getMethod(String name, Class<?>... parameterTypes):獲取一個非私有方法。name是方法名,parameterTypes是參數所屬類型的字節碼文件
Method[] getMethods()   
Method getDeclaredMethod(String name, Class<?>... parameterTypes) //根據參數類型、個數獲取相應非私有方法的類。
Method[] getDeclaredMethods();獲取所有方法的類。
使用字節碼方法:Method的方法
jdk1.5 public Object invoke(Object obj,Object... args);//obj:調用方法的對象 args:調用方法時傳入的參數。
  jdk1.4 public Object invoke(Object obj,Object[] args); //同上
注意:invoke方法,會對傳入的部分 類型的參數(數組、Object)進行一次自動解包,會將拆包後的數據作爲參數進行傳遞,所以使用時要注意。
  如果獲得的方法靜態的,不需要對象調用,那麼invoke的第一個參數就用null表示。
代碼演示:
  //調用任意一個類的main方法
  思路:將被調用類的類名傳給運行類的main方法,數組args接收這個類名-->獲取到類名,根據類名得到字節碼文件-->調用getMethod獲取main方法-->方法調用
  在eclipse中,運行時給main傳遞參數的方法:右擊-->Run As-->run configratiion-->Arguments,在Program arguments一欄填入參數,點擊Run即可。
  String startingClassName=args[0];
//Method mainMethod=startingClassName.getClass().getMethod("main",String[].class );
Method mainMethod=Class.forName(startingClassName).getMethod("main",String[].class );
mainMethod.invoke(null, new Object[]{new String[]{"123","abc","456"}});
mainMethod.invoke(null, (Object)new String[]{"123","abc","456"});//這裏把傳入的數組對象向上轉型爲Object了,因爲invoke方法會對數組類型的參數自動解包一次,
如果直接傳入數組,打開包就看到了3個字符串對象,會將3個對象作爲參數傳遞,也就是傳入了3個參數而main方法需要的是一個字符串數組參數。
  所以就將數組再封裝一次,成了Object,invoke解包後正好是一個字符串數組。
3.4 數組反射:數組的類型和維數都相同時,數組使用的是同一字節碼文件。
數組字節碼的名稱是:[I-->一維整形數組;[[I-->二維整形數組;[L java.lang.String;-->一維字符串數組
數組字節碼的父類是(調用getSuperclass):Object類對應的Class,任意類型的都是。 但基本數據類型不能轉換成Object
基本類型的一維數組可以被當做Object類型使用,不能當做Object[]使用;非基本類型的一維數組,既可以當做Object使用又可以當做Object[]使用。
Arrays.aList()方法在處理int[]和String[]時的差異:
查看數組內容使用操作數組的工具類Arrays的aList時要注意:當傳入基本類型的數組時,會按照1.5把數組當成一個對象傳入,List中就只有一個元素;當傳入Object子類對象的數組時,會按1.4傳入一個數組,List中就有多個元素。
Jdk1.5 public static <T> List<T> asList(T... a);返回一個受指定數組支持的固定大小的列表
Jdk1.4  public static List asList(Object[] a);

int[] a1=new int[]{1,2,3}; int[][] a3=new int[2][3]; String[] a4=new String[]{"a","b","c"};
System.out.println(Arrays.asList(a1));//[[I@16a6a7d2]
System.out.println(Arrays.asList(a3));//[[I@6e267b76, [I@2073b879]
System.out.println(Arrays.asList(a4));//[a, b, c]
Array工具類用於完成對數組的反射操作,用Array工具類:
思路:將一個數組轉化爲對象obj-->用對象獲取字節碼文件,判斷是否爲數組Class-->是,Array.getLength(obj)獲取長度,for循環一個個輸出數組元素Array.get(obj,index);否,直接輸出obj。
思考:怎麼得到一個數組中的元素類型?沒有方法可以得到。Object[] obj=new Object[]{"a",1};
int[] a1=new int[]{1,2,3}; Object obj=(Object)a1;
if(obj.getClass().isArray()){
int len=Array.getLength(obj);
for(int i=0;i<len;i++){
System.out.println(Array.get(obj, i));
}
}else{
System.out.println(obj);

三、什麼時候會用到反射?框架:Spring、Struts、Hibernate都會用到反射技術。
反射的作用-->實現框架功能
框架:框架用來調用用戶提供的類(而工具類是被用戶調用的),框架在開發中經常用到,因爲效率高。賣的房子是框架,用戶之後安裝的門
窗空調等就是用戶提供的類,在做框架時還不知道用戶提供的類是什麼,所以在寫框架時,不能直接new一個類的對象,而要從配置文件中讀取。
用戶寫的類,只需按鍵值對的方式存放如入置文件中。
/* 用 配置文件+反射 實現簡單的反射。用戶調用的類是ArrayList或HashSet
思路:將配置文件內容加載到內存的集合當中-->獲取到值(要調用的類的類名)-->獲取字節碼文件創建該類對象。*/
InputStream in=new FileInputStream("config.properties");
Properties prop=new Properties();//HashMap的優化,可以在加載時將硬盤文件中的鍵值對存入內存當中,也可以從內存存儲到文件中,而HashMap需要一次次手動加載。
prop.load(in);
in.close();//關閉與系統關聯的內容。若沒有,則當清除了java對象時,與系統的關聯還未關閉。java對象是有java虛擬機中的垃圾回收機制來清理的。
String className=prop.getProperty("className");
Collection collection=(Collection)Class.forName(className).newInstance(); 
/*配置文件位置?
*框架用加載器加載配置文件 
*配置文件應放在classpath指定的目錄,即與class文件放在同一目錄下。因爲給客戶的是class文件的jar包。
*在eclipse開發中,放到java文件所在的包下。因爲eclipse會自動將配置文件也放入class文件所在目錄,即classpath指定的目錄bin下。
*/
//獲取字節碼文件-->獲取字節碼文件加載器-->返回讀取指定資源的輸入流
//InputStream in=ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");//要寫出cn/itcast/day1/,因爲class文件和配置文件在bin下的cn/itcast/day1/下。

//獲取字節碼文件-->查找具有給定名稱的資源(Class的getResourceAsStream方法內部封裝了獲取加載器的方法),此時不加/使用的是與class文件的相對路徑,所以也不用加cn/itcast/day1/。若配置文件不在class文件的目錄中,就用絕對路徑-->/+目錄

InputStream in=ReflectTest2.class.getResourceAsStream("config.properties");


---------------------- android培訓java培訓、期待與您交流!

                                 ----------------------詳細請查看:http://edu.csdn.net/heima?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章