1. 程式人生 > >java基礎增強---反射

java基礎增強---反射

一、反射的基石–Class類
1、Java程式中的各個java類屬於同一事物,描述這些類事物的java類名就是Class。
2、對比提問:從多的人用一個什麼類表示?從多的java類用一個什麼類表示?
人類—Person
java類–Class
注意這裡Class是大寫的,不是關鍵字class。
3、對比提問:
1)Person類代表人,它的例項物件就是張三,李四這樣一個個具體的人,
2)Class類代表java類,它的各個實現物件又分別對應什麼呢?
對應各個類在記憶體中的位元組碼,例如Person類的位元組碼,ArrayList類的位元組碼,等待。
一個類被類載入器載入到記憶體中,佔用一片儲存空間,這個空間裡面的內容就是類的位元組碼,
不同的類的位元組碼是不同的,所以它們在記憶體中的內容是不同的,這一個個的空間分別用一個個的物件來表示,這些物件顯然具有相同的型別,這個型別是什麼呢?
4、得到位元組碼物件的三種方法
1)類名.class,例如System.class
2)物件.getClass(),例如new Date().getClass()
3)Class.forName(“完整類名”),例如 Class.forName(“java.util.Data”);
反射時主要用第三種。它是靜態方法。
面試題:Class.forName()的的作用是什麼?
獲取一個類的位元組碼物件,如果該類的位元組碼已經在記憶體中存在,就可以直接呼叫,
如果還沒有存在,就呼叫類載入器進行載入,然後獲取該類的位元組碼物件。
5、九個預定義的Class物件:
參看 Class.isPrimitive方法的幫助文件
八個基本型別和void,分別是:boolean、byte、char、short、int、long、float、double和void。
int.class == Integer.TYPE
6、陣列型別的Class例項物件用的方法是:
Class.isArray()
7、總之,只要是在源程式中出現的型別,都有各自的Class例項物件,
例如int[],void。

例:獲取String類的位元組碼的三種方法

class Demo1{
    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);               //true
System.out.println(cls1 == cls3); //true //是否是原始型別 System.out.println(cls1.isPrimitive()); //false System.out.println(int.class.isPrimitive()); //true System.out.println(int.class == Integer.class); //false System.out.println(int.class
== Integer.TYPE); //true } }

二、反射的概念
反射,就是把java類中的各種成分對映成相應的java類。
也可以理解成反射就是程式自己能夠檢測自身資訊,就像人會通過鏡子來檢視自己的身體。
例如,一個java類中用一個Class類的物件來表示,一個類中的組成部分:成員變數,方法,構造方法,包等等資訊也是用一個個java類來表示。
就像騎車是一個類,騎車中的發動機,變速箱等等也是一個個類,表示java類的Class類顯然要提供一系列的方法,來獲得其中的變數,方法,構造方法,修飾符,包等資訊,這些資訊就是用相應類的例項物件來表示,他們是Field、Method、Contructor、Package等等。
一個類中的每個成員都可以用相應的反射API類的一個例項物件來表示,通過呼叫Class類的方法可以得到這些例項物件後,得到這些例項物件胡有什麼用?怎麼使用?這這是學習和應用反射的要點

例:

class Demo2{
    public static void sop(Object obj){System.out.println(obj);}
    public static void main(String[] args) throws Exception {
        String s1 = "1234";
        Class c1 = s1.getClass();
        Class c2 = String.class;
        Class c3 = Class.forName("java.lang.String");
        sop(c1==c2);                    //c1與c2是否是同一個物件true
        sop(c1==c3);                    //c1與c3是否是同一個物件true
        sop(String.class.isPrimitive());//String是否是基本型別false
        sop(int.class.isPrimitive());   //int是否是基本型別true
        sop(int.class==Integer.class);  //int與Integer的位元組碼是否是同一個物件false
        sop(int.class==Integer.TYPE);   //int與Integer.TYPE的位元組碼是否是同一個物件true
        sop(int[].class.isPrimitive()); //int[]是否是基本型別false
        sop(int[].class.isArray());     //int[]是否是陣列型別true
    }
}

三、構造方法的反射
Constructor 類
1、Constructor類代表某個類中的一個構造方法。
2、得到某個類所有的構造方法:
例子:
Constructor constructor[] =
Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
3、建立例項物件:
通常方式:String str = new String(new StringBuffer(“abc”));
反射方式:String str = (String)constructor.newInstance(new StringBuffer(“abc”));
呼叫獲得的方法時要用到上面相同型別的例項物件。
4、Class.newInstance()方法:
例子:String obj = (String)Class.forName(“java.lang.String”).newInstance();
該方法內部先得到預設的構造方法,然後用該構造方法建立例項物件。
該方法內部的具體程式碼是怎麼樣寫的呢?用到了緩衝機制來儲存預設構造方法的例項物件。
一個類有多個構造方法,用什麼方式可以區分清楚想要得到其中的哪個方法呢?根據引數的個數和型別,
例如,Class.getMethod(name.Class…args)中的args引數就代表索要獲取的那個方法的各個引數的型別的列表。
重點:引數型別用什麼方式表示?用Class例項物件。
例如:
int.class,(int []).class
int [] ints = new int[0];
ints.getClass();

需求:反射String類的 String(StringBuffer buffer) 這個構造方法

/**
*思路:
*1、通過String類的位元組碼物件呼叫getConstructor方法獲取這個類的構造方法。
*2、具體要獲得哪個構造方法,就給這個方法傳遞一個引數型別。這個引數型別是Class物件的一個數組。
*3、用第一步返回的一個Constructor物件呼叫newInstance方法,建立StringBuffer的例項物件。
*/
import java.lang.reflect.Constructor;
public class D19_ReflectConstructor {
    public static void main(String[] args) throws Exception {
        Constructor<String> c = String.class.getConstructor(StringBuffer.class);
        String s = (String)c.newInstance(new StringBuffer("abc"));
        System.out.println(s);
    }
}
/*
結果
abc
*/

四、成員變數的反射
Field類
Field 提供有關類或介面的單個欄位的資訊,以及對它的動態訪問許可權。
反射的欄位可能是一個類(靜態)欄位或例項欄位。
部分方法:
Object get(Object obj) 返回指定物件上此 Field 表示的欄位的值。
void set(Object obj, Object value) 將指定物件變數上此 Field 物件表示的欄位設定為指定的新值。
String getName() 返回此 Field 物件表示的欄位的名稱。
Class

import java.lang.reflect.Field;
//定義一個用來反射的類
class ClassPoint { 
    public int x;
    private int y;
    public ClassPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
public class D20_ReflectField {
    public static void main(String[] args) throws Exception {
        ReflectField();
    }
    //對ClassPoint繼承成員變數反射
    public static void ReflectField() throws Exception {
    //建立該類的例項物件
        ClassPoint cp = new ClassPoint(3,5);

        //反射變數 x
        //獲取物件的位元組碼,根據位元組碼獲得x對應的成員欄位
        Field fieldX = cp.getClass().getField("x");
        //通過欄位fieldX獲取它對應的值
        System.out.println("變數x的值:"+fieldX.get(cp));

        //反射私有成員變數y--暴力反射
        //getDeclaredField()獲取宣告的欄位,不管是被什麼修飾的,但只要不是繼承的。
        Field fieldY = cp.getClass().getDeclaredField("y");
        fieldY.setAccessible(true);
        //setAccessible()取消的欄位的許可權檢查,false表示要檢查
        fieldY.setAccessible(true);
        System.out.println("變數y型別:"+fieldY.getType());//獲取該欄位對應的變數型別
        System.out.println("變數y名稱:"+fieldY.getName());//獲取該欄位對應的變數名 
        System.out.println("變數y的值:"+fieldY.get(cp));  //獲取該欄位對應的變數的值
    }
}
/*
結果
變數x的值:3
變數y型別:int
變數y名稱:y
變數y的值:5
*/

五、成員變數反射的綜合例項
需求:反射某個類中所有的String型別的成員變數,並將該變數的值中指定的字元替換成新的字元。
分析:其實就是通過反射用用新字串替換所有String型別的原來的字串
思想:
1、定義StringDemo類,類裡定義多種型別的成員變數,有的被public修飾。
2、另外定義一個類實現對StringDemo類的反射和其他操作,該類首先建立StringDemo的例項物件。
3、用getFields方法返回一個Field陣列,用getFields方法是限制了只能反射被public修飾的成員欄位。
4、變數該Field陣列,取出每個欄位,然後用該欄位獲取它的宣告型別的Class物件與String.class比較。
5、如果是同一份位元組碼,就用set方法把該欄位的值用新的值替換掉。

例項:通過反射把String型別的變數的值替換成新值

import java.lang.reflect.Field;
class StringDemo { 
    public int x = 0;
    public String str1 = "wuguangxin";
    public String str2 = "howareyou";
    String str3 = "jiewin";
}
public class D21_ReflectFieldTest {
    public static void main(String[] args) throws Exception {
        changeStringValue();
    }
    public static void changeStringValue() throws Exception{
        //建立物件
        StringDemo str = new StringDemo();
        //用getFields()方法返回一個所有宣告的公共欄位陣列
        Field[] fields = str.getClass().getFields(); 
        //變數該陣列,獲取每一個欄位進行
        System.out.println("把u替換成*");
        for (Field field : fields){
            //如果該欄位的位元組碼和String的位元組碼相同,說明是同一份位元組碼。
            //位元組碼最適合用==比較,不建議用equals
            if(field.getType() == String.class){
                //獲取原來的欄位值
                String oldValue = (String)field.get(str);
                //把原來的值的指定字元替換成指定字元
                String newValue = oldValue.replace("u", "*");
                //設定該欄位對應的變數的值為新的值
                field.set(str, newValue);
                System.out.println(oldValue+" --> "+newValue);//測試
            }
        }
    } 
}
/*
結果:
把u替換成*
wuguangxin --> w*g*angxin
howareyou --> howareyo*
*/

六、成員方法的反射
Method 類
Method 提供關於類或介面上單獨某個方法(以及如何訪問該方法)的資訊。
所反映的方法可能是類方法或例項方法(包括抽象方法)。
方法:
String getName() 返回此 Method 物件表示的方法名稱。
boolean isVarArgs() 如果將此方法宣告為帶有可變數量的引數,則返回 true;否則,返回 false。
boolean isSynthetic() 如果此方法為複合方法,則返回 true;否則,返回 false。
String toGenericString() 返回描述此 Method 的字串,包括型別引數。
Object invoke(Object obj, Object… args) 對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法。
invoke方法引數:
obj - 從中呼叫底層方法的物件
args - 用於方法呼叫的引數
返回:使用引數 args 在 obj 上指派該物件所表示方法的結果

例:成員方法的反射

import java.lang.reflect.Method;

public class D22_ReflectMethod {
    public static void main(String[] args) throws Exception {
        reflectMethod();
    }
    /**
     * 利用反射呼叫String類的charAt方法來獲取字串str的指定值。
     * Method代表位元組碼的方法
     * 從String獲取位元組碼,根據位元組碼獲取方法,引數(要獲取的方法名稱,引數列表)
     * @throws Exception
     */
    public static void reflectMethod() throws Exception {
        String str = "abc";
        Method methodCharAt = String.class.getMethod("charAt", int.class);
        System.out.println(methodCharAt.invoke(str,1));
    }
}
/*
結果
b
*/

七、 對接收陣列引數的成員方法進行反射

例:對接收陣列引數的成員方法進行反射

package zxx.enhance;

import java.lang.reflect.Method;

public class D23_ReflectArrayMethod {
    public static void main(String[] args) throws Exception {
        reflectArrayMethod(args);
    }
    public static void reflectArrayMethod(String[] args) throws Exception {
        //普通方式呼叫main方法
        //TestArguments.main(new String[]{"abcd","123","eeeee"});

        //反射方式呼叫main方法。
        String srartingClassName = args[0];
        Method mainMethod = Class.forName(srartingClassName).getMethod("main", String[].class);
        //null:因為main方法是靜態的,呼叫靜態方法不需要物件

        //這樣會報錯,說引數個數不對,怎麼解決這個問題?
        //mainMethod.invoke(null, new String[]{"abcd","123","eeeee"});
        //上面語句報錯的原因是:因為接收的引數是1個,而new String[]{"abcd","123","eeeee"}是一個數組,
        //會被拆成單個的元素,於是就出現了3個引數,為了把以上的寫法當做一個引數,有2種方法:

        //解決方法1:把這個陣列在進行一次包裝,把它作為一個元素封裝到Object陣列中。
        //mainMethod.invoke(null, new Object[]{new String[]{"abcd","123","eeeee"}});

        //解決方法2:型別轉換,在前面加上(Object)轉換型別,告訴編譯器,這是一個Object型別的引數,不要拆包。此效率比較高
        //main方法是靜態的,所以invoke方法的引數用null
        mainMethod.invoke(null, (Object)new String[]{"abcd","123","eeeee"});
    }
}
//注意:在執行時首先要獲取此類的完整路徑“zxx.enhance.TestArguments”,就是包名+類名。
//然後點右鍵-->Run As-->Run Configurations-->(x)=Arguments,在裡面貼上剛才複製的。儲存後在執行。
class TestArguments{
    public static void main(String[] args) throws Exception {
        for(String arg : args){
            System.out.println(arg);
        }
    }
}
/*
結果
abcd
123
eeeee
*/

八、陣列與Object的關係及其反射型別

package zxx.enhance;

public class D24_ArrayAndObject {
    //反射陣列。列印物件中成員方法
    public static void main(String[] args) {
        int[] a1 = new int[3];
        int[] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String[] a4 = new String[3];
        System.out.println(a1.getClass() == a2.getClass());
        //System.out.println(a1.getClass() == a4.getClass());
        //System.out.println(a1.getClass() == a3.getClass());

        System.out.println(a1.getClass().getName());
        //獲取他們的父類的位元組碼名稱
        System.out.println(a1.getClass().getSuperclass().getName());
        System.out.println(a2.getClass().getSuperclass().getName());
        System.out.println(a3.getClass().getSuperclass().getName());
        System.out.println(a4.getClass().getSuperclass().getName());

        Object aObj1 = a1;
        Object aObj2 = a4;
        //基本型別的一維陣列不能轉換為Object型別陣列。
        //因為Object這個數組裡面裝的是int型別的陣列,不是Object
        //Object[] aObj3 = a1;
        Object[] aObj4 = a3;
        Object[] aObj5 = a4;
        System.out.println(aObj1);
        System.out.println(aObj2);
        System.out.println(aObj4);
        System.out.println(aObj5);
    }
}
/*
結果
true
[I
java.lang.Object
java.lang.Object
java.lang.Object
java.lang.Object
[[email protected]
[Ljava.lang.String;@525483cd
[[[email protected]
[Ljava.lang.String;@525483cd
*/

八、陣列與Object的關係及其反射型別

package zxx.enhance;

public class D24_ArrayAndObject {
    //反射陣列。列印物件中成員方法
    public static void main(String[] args) {
        int[] a1 = new int[3];
        int[] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String[] a4 = new String[3];
        System.out.println(a1.getClass() == a2.getClass());
        //System.out.println(a1.getClass() == a4.getClass());
        //System.out.println(a1.getClass() == a3.getClass());

        System.out.println(a1.getClass().getName());
        //獲取他們的父類的位元組碼名稱
        System.out.println(a1.getClass().getSuperclass().getName());
        System.out.println(a2.getClass().getSuperclass().getName());
        System.out.println(a3.getClass().getSuperclass().getName());
        System.out.println(a4.getClass().getSuperclass().getName());

        Object aObj1 = a1;
        Object aObj2 = a4;
        //基本型別的一維陣列不能轉換為Object型別陣列。
        //因為Object這個數組裡面裝的是int型別的陣列,不是Object
        //Object[] aObj3 = a1;
        Object[] aObj4 = a3;
        Object[] aObj5 = a4;
        System.out.println(aObj1);
        System.out.println(aObj2);
        System.out.println(aObj4);
        System.out.println(aObj5);
    }
}
/*
結果
true
[I
java.lang.Object
java.lang.Object
java.lang.Object
java.lang.Object
[[email protected]
[Ljava.lang.String;@525483cd
[[[email protected]
[Ljava.lang.String;@525483cd
*/

九、陣列的反射
具有相同維數和元素型別的陣列屬於同一個型別,即具有相同的Class例項物件。
代表陣列的Class例項物件的getSuperClass()方法返回的父類為Object類對應的Class。
基本型別的一維陣列可以被當作Object型別使用,不能當作Object[]型別使用,
非基本型別的一維陣列,既可以當做Object型別使用,又可以當做Object[]型別使用。
Arrays.asList()方法處理int[]和String[]時的差異。
Array工具類用於完成對陣列的反射操作。

例:陣列的反射

package zxx.enhance;

import java.lang.reflect.Array;

public class D25_ReflectArray {
    public static void main(String[] args) {
        reflectArray();
    }
    //反射陣列。列印物件中成員方法
    private static void reflectArray() {
        String[] obj = {"A","B","C"};
        //String obj ="ABC";

        Class<? extends String[]> cla = obj.getClass();
        //如果是一個數組,就拆成單個元素打印出來。
        if(cla.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);
        }
    }
}
/*
結果
A
B
C
*/

十、集合的反射
*框架的概念及用反射技術開發框架的原理
*用類載入器的方式管理資源和配置檔案
反射的作用 – 實現框架功能
1、框架
我做房子賣給使用者,由使用者自己安裝門窗和空調,我做的房子就是框架,
使用者需要使用我的框架,把門窗插入進我的框架中。
框架與工具類有區別,工具類被使用者的類呼叫,而框架則是呼叫使用者提供的類。
2、框架要解決的核心問題
我在寫框架(房子)時,你這個使用者可能還在讀小學,還不會寫程式,
那麼我寫的框架程式怎麼呼叫你以後寫的類(門窗)呢?
因為在寫程式時無法知道要被呼叫的類名,所以,
在程式中無法直接new某個類的例項物件,而是要用凡是的方式來做。
3、綜合例項:
先直接用new語句建立ArrayList和HashSet的例項物件,演示用eclipse自動生成
Reflection類的equals和hashcode方法,比較兩個集合的執行結果差異。
然後改為採用配置檔案加反射的凡是建立ArrayList和HashSet的例項物件,
比較觀察執行結果差異。
需求:利用反射實現框架功能
思路:
1)通過檔案輸出流,建立一個檔案Config.properties,
2)獲取”className=java.util.ArrayList“的位元組碼後存入檔案中。檔案可以手動建立。
3)建立檔案輸入流和一個屬性集,讀取指定檔案內容,載入到屬性集中。
4)用鍵”className“在屬性集中收索。
5)獲得新集合,往集合中新增元素。
6)列印集合

例:

package zxx.enhance;

import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

public class D27_ReflectCollcetion {
    public static void main(String[] args) throws Exception {
        reflectCollcetion();
    }
    //反射集合
    public static void reflectCollcetion() throws Exception {
        //實際開發中,配置檔案的路徑不是這麼寫的,必須要寫完整的路徑。
        //InputStream is = new FileInputStream("config.properties");
        InputStream is = ReflectDemo.class.getResourceAsStream("config.properties");
        Properties props = new Properties();
        props.load(is);
        //關閉的是is物件關聯的那個物理資源,物件本身並沒有關閉,
        //而是有java的垃圾回收機制處理的。
        is.close();

        //反射做法
        String className = props.getProperty("className");
        Collection<ClassPoint> collections = (Collection)Class.forName(className).newInstance();

        //原始做法
        //Collection<ClassPointDemo> collections = new HashSet<ClassPointDemo>();
        ClassPoint cp1 = new ClassPoint(3,3);
        ClassPoint cp2 = new ClassPoint(5,5);
        ClassPoint cp3 = new ClassPoint(3,3);
        collections.add(cp1);
        collections.add(cp2);
        collections.add(cp3);
        collections.add(cp1);

        //更改config.properties配置檔案裡的ArrayList為HashSet後,列印結果將不同
        //因為集合的儲存方式不同,ArrayList可以有重複元素,是有序的
        //而HashSet集合是無序的,不可以重複儲存,因為該集合會判斷hashCode和equals方法。
        System.out.println("集合元素個數:"+collections.size());
    }
}
/*
結果
集合元素個數:4
*/

十一、通過反射獲得泛型的實際型別引數
分析:
比如:Vector v = new Vector();
那麼通過v是無法知道定義它的那個泛型型別的,那麼可以把這個v交給一個方法當做引數或者返回值型別來使用,
然後通過Method類的getGenericParameterTypes()方法來獲得該方法的引數列表,從而獲得引數實際間型別。

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Date;
import java.util.Vector;

/**
 * 通過反射獲得泛型的實際型別引數
 * @author Administrator
 */
public class GenericTest {
    public static void main(String[] args) throws Exception {
        //通過v物件是無法得到的,值能通過方法來獲取,那麼就要定義一個方法如下面applyVector方法。
        //Vector<Date> v = new Vector<Date>();

        //獲得位元組碼,通過位元組碼獲得方法,引數是一個方法名,Vector的位元組碼
        Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);

        //通過Method類的getGenericParameterTypes()方法
        //反射applyMethod方法的引數化型別,可能有多個,所以是陣列。
        Type[] types = applyMethod.getGenericParameterTypes();

        //返回types的第一個引數,返回的是ParameterizedType型別。
        ParameterizedType pType = (ParameterizedType)types[0];

        //獲得原始型別
        System.out.println(pType.getRawType());

        //獲得實際型別。
        System.out.println(pType.getActualTypeArguments()[0]);
    }

    //需要定義這個方法,通過這個方法的引數列表來反射他的引數型別。
    public static void applyVector(Vector<Date> v) {
    }
}
/*
結果
class java.util.Vector
class java.sql.Date
*/

十二、反射介面中的成員變數

package zxx.enhance;

import java.lang.reflect.Field;

interface Inter{
    public int z = 8;
}
class ClassPoint1 implements Inter{
    public int x;
    @SuppressWarnings("unused")
    private int y;
    public ClassPoint1(int x, int y){
        this.x = x;
        this.y = y;
    }
}
public class D28_ReflectIntefaceField{
    public static void main(String[] args) throws Exception{
        ClassPoint1 cp = new ClassPoint1(3, 5);
        //先得到介面的Class物件陣列
        for(Class<?> ca : cp.getClass().getInterfaces()){
            //得到Field物件陣列
            ca.getFields();
            //遍歷陣列得到所有物件的名字
            for(Field fd : ca.getFields()){
                System.out.println(fd.getType());   //獲得型別
                System.out.println(fd.getName());   //獲得名稱
                System.out.println(fd.get(cp));     //獲得值
            }
        }
    }
}
/*
結果
int
z
8
*/