JAVA提高三:反射總結


為前期學習過反射,再這里再次復習總結下:【轉載請說明來源:http://www.cnblogs.com/pony1223/p/7659210.html 】

一、透徹分析反射的基礎_Class類

Class是一個類,他代表一類事物,它代表一類什么樣的事物呢? Java 程序中的各個java類屬於同一類事物,描述這類事物的java類名就是Class.

思考:
眾多的人可以用一個什么類表示? Person
眾多的類用一個什么類來表示?    答案是Class

我們知道Person代表一個人,一個人有他的身高,體重等屬性,有叫,喊等方法,人還有他的父類,它的構造方法.接口等.
那么Class類呢?他是所有類的類.那么他可以干什么呢?他可以獲取某一個類的屬性,方法,父類,構造方法,接口等....

(1)Class的本質是什么?

我們現在定義兩個對象:
Person p1 = new Person();
Person p2 = new Person();
那如何獲取這兩個對象的Class類呢?用Class cls1 = new Class()可以么?不可以,因為Class跟們就沒有這個構造方法.
Class類在內存中代表的就是一段字節碼,那什么是字節碼呢?當我們在源程序用到Person這個類的時候,首先把這個類編譯成class文件以后,存放到硬盤上,這些class文件其實就是一堆二進制代碼,要把這一堆二進制代碼加載到內存中來,然后才可以去創建一個個對象.當我在程序中用好多java類,有Person類,Math類,Date類,我又到這三個類,那么在內容中會有幾個字節碼呢?有三份字節碼,每一份字節碼就是Class的一個實例對象.

Class cls1 = Date.class;//代表Date類的字節碼
Class cls2 = Pserson.class;//代表Person類的字節碼

(2)那如何來得到這個Class呢?

第一種,就是類.class;
第二種得到字節碼的方式是,用這個類的實例p1.getClass(),也可以返回這個類對應的字節碼
第三種方式:Class.forName("java.lang.String"),里面需要制定類的完整的名字,這個方法的作用也是得到java.lang.String類的字節碼.
Class.forName()的作用:
返回指定類的字節碼.返回的方式有兩種.第一種.這個字節碼曾經被加載過,已經保存在java虛擬機中了,直接返回.另一種是,java虛擬機中還沒有這個字節碼,則用類加載器去加載,把加載進來的字節碼就會存在java虛擬機里面了.以后要在等到這份字節碼就不用再加載了.
將類轉變成字節碼有以上三種形式,而在反射中使用最多的是第三種方法,Class.forName(name),因為寫源程序的時候,還不知道類的名字,可以作為變量傳遞.
九個預定義Class實例對象:
java中有八個基本類型(int ,byte,long,float,double,short,char,boolean,)和void,他們也對應有Class對象

總結:一個類的Class字節碼只存在一份.

Class.isPrimitive()方法.判斷一個類型是否是基本的數據類型.

 總結:只要在源程序中出現的類型,都有各自的Class實例對象,例如:int[], void...

 二、理解反射的概念

第一種官方理解:

反射是指在運行狀態中,對於任意一個類,都可以獲取到這個類的所有屬性和方法;對於任意一個對象,都能夠調用這個對象的任意方法和屬性;這種動態獲取信息及動態調用對象的方法,稱為JAVA語言的反射機制。

第二種個人理解:

反射,就是把Java類中的各個成分映射成相應的Java類.我們想想,一個類身上有什么,有包,有方法,有屬性,有構造方法,有接口.而這些方法相應的又返回一個類.看下面這個api,通過Class的方法可以得到什么.

 

 

三、構造方法的反射應用

package study.javaenhance;

import java.lang.reflect.Constructor;

public class ReflectTest
{
    public static void main(String[] args) throws Exception 
    {
        String str = "abc";
        Class clz1 = String.class;
        Class clz2 = str.getClass();
        Class clz3 = Class.forName("java.lang.String");
        System.out.println(clz1 ==  clz2);
        System.out.println(clz1 ==  clz3);
        getConstructor();
    }
    
    /**
     * 目前String 類型中有很多構造方法,我想要找到獲取構造方法為StringBuffer類型的一個
     * @throws Exception 
     * @throws SecurityException 
     */
    public static void getConstructor() throws SecurityException, Exception
    {
        String.class.getConstructor(StringBuffer.class);
        //那么上面的獲取到構造方法有什么用呢?
        /**
         * 通常,我們創建String 對象為 String abc = new String(new StringBuffer("abc")) 方式,
         * 下面我們通過反射來實現:
         *  1.加載類
         *  2.解析類
         *  
         */
        Constructor constructor = String.class.getConstructor(StringBuffer.class);
        String str =  (String) constructor.newInstance(new StringBuffer("abc"));
        System.out.println(str);
    }

}

 

具體的原理,如何使用可以參考:http://www.cnblogs.com/pony1223/p/7445950.html

 四、成員變量的反射

成員變量的反射,在這里我們先定義一個類,用於講解成員變量的反射。

 

package study.javaenhance.util;import java.util.Date;

public class ReflectPoint {
    private Date birthday = new Date();
    
    private int x;
    public int y;
    public String str1 = "ball";
    public String str2 = "basketball";
    public String str3 = "itcast";
    
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final ReflectPoint other = (ReflectPoint) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }


    @Override
    public String toString(){
        return str1 + ":" + str2 + ":" + str3;
    }


    public int getX() {
        return x;
    }


    public void setX(int x) {
        this.x = x;
    }


    public int getY() {
        return y;
    }


    public void setY(int y) {
        this.y = y;
    }


    public Date getBirthday() {
        return birthday;
    }


    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    
}

接下來,我們要采用反射的方式來獲取其中的x 成員和 y成員

//成員變量的反射
        //1.首先我們先初始化一下ReflectPoint類的xy值,以便反射的時候獲取其值
        ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后續加載類
        Field xField = rp.getClass().getField("x");
        //3.解析類
        System.out.println(xField.get(rp));

上述會出現一個錯誤:

Exception in thread "main" java.lang.NoSuchFieldException: x
at java.lang.Class.getField(Class.java:1520)
at study.javaenhance.ReflectTest.main(ReflectTest.java:25)

 也就是說找不到這個,為什么呢?很明顯,因為是私有的,因此需要采用另外一個方法來獲取,修改如下:

ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后續加載類
        Field xField = rp.getClass().getDeclaredField("x");
        //3.解析類
        System.out.println(xField.get(rp));

Exception in thread "main" java.lang.IllegalAccessException: Class study.javaenhance.ReflectTest can not access a member of class study.javaenhance.util.ReflectPoint with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Field.doSecurityCheck(Field.java:960)
at java.lang.reflect.Field.getFieldAccessor(Field.java:896)
at java.lang.reflect.Field.get(Field.java:358)
at study.javaenhance.ReflectTest.main(ReflectTest.java:27)

現在是看到,卻沒法拿到,需要修改訪問的權限打開,修改為如下:

ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后續加載類
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析類
        System.out.println(xField.get(rp));

對於非私有的成員變量,沒有這么麻煩,如下即可:

//成員變量的反射
        //1.首先我們先初始化一下ReflectPoint類的xy值,以便反射的時候獲取其值
        ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后續加載類
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析類
        System.out.println(xField.get(rp));
        Field yField = ReflectPoint.class.getField("y");
        System.out.println(yField.get(rp));

下面做一個小例子,我們看到ReflectPoint類中有很多成員變量,我們現在要做的是將String類型的成員變量的值中含b的字母替換稱為a,如:ball 替換為aall

代碼如下:

private static void changeStringValue(Object rp) throws Exception
    {
        //思路:1.反射獲取要所有的成員變量  2.獲取成員變量的類型判斷是否為String類型 3.如果是則獲取其值 4.替換 5.回填
        Field[] fields = rp.getClass().getFields();
        for (Field field : fields) {
            if(field.getType() == String.class)
            {
                String oldValue = (String) field.get(rp);
                String newValue = oldValue.replace("b", "a");
                field.set(rp, newValue);
            }
        }
    }

客戶端調用代碼:

 

ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后續加載類
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析類
        System.out.println(xField.get(rp));
        Field yField = ReflectPoint.class.getField("y");
        System.out.println(yField.get(rp));
        
        //案例:changeStringValue
        System.out.println(rp);
        changeStringValue(rp);
        System.out.println(rp);

結果為:

2
4
ball:basketball:itcast
aall:aasketaall:itcast

五、成員方法的反射

//方法的反射
        Method methodCharAt = String.class.getMethod("charAt", int.class);
        System.out.println(methodCharAt.invoke(str, 1));
        //兼容JDK5.0之前的寫法
        System.out.println(methodCharAt.invoke(str, new Object[]{2}));

可以看出還是比較簡單的和成員的方式差不多,但是下面需要注意的是,如果有一個類,我們需要通過反射的方式去調用main方法,該如何去做呢?

先定義一個類:

class TestArguments{
    public static void main(String[] args){
        for(String arg : args){
            System.out.println(arg);
        }
    }
}

最先想到的代碼如下:

//調用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 這種方式,因為有些場景我們的類是傳入的是你運行期才知道的
        Class clazz = Class.forName("study.javaenhance.TestArguments");
        Method method =  clazz.getMethod("main",String[].class);
        //因為main方法是靜態方法,所以第一個參數傳入的為null
        method.invoke(null,new String[]{"1","2","3"});

結果如下:

為什么會這樣呢?我們本身的使用其實是正確的,只是因為SUN公司的JDK5.0的可變參數出現后,為了兼容以前的數組方式所以出現了上面的錯誤。

在JDK5.0前:public Object invoke(Object obj, Object... args)   是采用的為 public Object invoke(Object obj, Object[] obj)

采用的數組方式進行的接受,那么這個時候你傳入的是String[]{"1","2","3"} 那么它正好和Object[] obj匹配,因為在1.5前就會開始拆分,那么就變成了傳入了3個String類的參數到了main方法上面,因此這個時候就參數類型個數無法匹配了,所以就出現了上面的錯誤了,如何修改匹配呢?方式如下:

 

//調用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 這種方式,因為有些場景我們的類是傳入的是你運行期才知道的
        Class clazz = Class.forName("study.javaenhance.TestArguments");
        Method method =  clazz.getMethod("main",String[].class);
        //因為main方法是靜態方法,所以第一個參數傳入的為null
        //method.invoke(null,new String[]{"1","2","3"});
        //這樣拆開后就是main方法想要的參數了
        method.invoke(null,new Object[]{new String[]{"1","2","3"}});
        //這樣就是一個對象傳入了,不需要拆了
        method.invoke(null,(Object)new String[]{"1","2","3"});

六、數組與Object的關系

先看下面的例子:

//數組與Object的關系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object

從上面的例子說明:

具有相同的維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象

代表數組的Class實例對象的個頭Superclass方法返回的父類為Object類對應的class

基本類型的一維數組可以被當做Object類型使用個,不能當做Object[]類型使用;非基本類型的一維數組,既可以當做Object類使用,又可以當做Object[]類型使用 --- 這句話需要理解一下,參考下面的代碼:

 

//數組與Object的關系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
        
        Object aObj1 = a1; //因為a1 是一個int[] 類型的數組所以是object類型
        Object aObj2 = a4;//因為a4 是一個String[]類型的數組所以是Object類型
        //Object[] aObj3 = a1;// 報錯,因為a1 是一個int[]數組里面的元素都是int型,不是Object類型所以不可以如果改成包裝類就可以
        Object[] aObj4 = a3; //a3 是一個二維數組,那么里面的一維數組就是一個Object類型,所以可以匹配Object[]代表里面每個元素類型為Object
        Object[] aObj5 = a4; //String[] 里面的每一個元素都是String類型,也是Object類型 所以可以匹配.
        
        System.out.println(a1);
        System.out.println(a4);

 

我們發現最后a1 和 a4 的輸出是:

[I@87816d
[Ljava.lang.String;@422ede

這個是什么東西呢?我們找下JDK文檔看下:

在Class 的getName 方法中說明如下:

 

即代表的是int類型的數組和String類型的數組,但我們肯定不希望輸出這個樣子,我們希望的看到的是里面的值,那么肯定有人會說用遍歷的方法遍歷出來,當然這是一種方法,但沒有簡單的方法嗎?

 七、數組的反射應用

上面提到了遺留的問題,我們本節來解決,解決前我們先學習兩個類:Array類 和Arrays類

Array 類提供了動態創建和訪問 Java 數組的方法。需要說明下Array 是 數組的反射類,處於反射包下面,所以我們看到引用類型下對於類,我們是用Class  對於數組我們是用Array

Arrays 此類包含用來操作數組(比如排序和搜索)的各種方法。此類還包含一個允許將數組作為列表來查看的靜態工廠。

我們現在就是要利用Arrays類中的asList方法來完成顯示的目的.

 

//數組與Object的關系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
        
        Object aObj1 = a1; //因為a1 是一個int[] 類型的數組所以是object類型
        Object aObj2 = a4;//因為a4 是一個String[]類型的數組所以是Object類型
        //Object[] aObj3 = a1;// 報錯,因為a1 是一個int[]數組里面的元素都是int型,不是Object類型所以不可以如果改成包裝類就可以
        Object[] aObj4 = a3; //a3 是一個二維數組,那么里面的一維數組就是一個Object類型,所以可以匹配Object[]代表里面每個元素類型為Object
        Object[] aObj5 = a4; //String[] 里面的每一個元素都是String類型,也是Object類型 所以可以匹配.
        
        System.out.println(a1);
        System.out.println(a4);
        
        //Array應用
        System.out.println(Arrays.asList(a1));
        System.out.println(Arrays.asList(a4));

結果為:

[[I@87816d]
[a, b, c]

為什么呢?如果對前面的數組與Object 關系理解的話,這里就清楚了,因為在5.0之前沒有可變參數,因此傳入的a1 是int[]  這個時候在Object[] 數組接受的時候發現Object[] = a1 這個是不匹配的,因此這個時候它無法處理,於是會走可變參數可變參數的時候,於是會當做一個參數處理,那么就是List集合里面直接添加這個數組了,然后結果顯示為[[I@hashCode 了;而String[] 傳入的時候,因為Object[] = String[] 是匹配的於是會走5.0前的方式,於是會拆分,那么List集合中就添加了3個元素,所以就顯示出了上述的結果。

那么現在有一個需求:

寫一個方法,傳遞一個參數,判斷這個參數是一個數組,還是一個普通類型,如果是普通類型,我就直接把他打印出來。如果是數組,我們就遍歷后把值打印出來。(好處,就是不用每次都去for循環,只需要調用這個方法即可,公共性)

 

private static void printObject(Object obj)
    {
        Class clazz = obj.getClass();
        if(clazz.isArray())
        {
            //如果是數組,利用數組的反射類Array類來進行相關操作
            int len = Array.getLength(obj);
            for(int i=0;i<len;i++){
                System.out.println(Array.get(obj, i));
            }
        }
        else
        {
            System.out.println(obj);
        }
        
    }

 

即完成了之前的遺留問題.

到此基本的反射應用總結到這里,最后附上全代碼。

package study.javaenhance;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

import study.javaenhance.util.ReflectPoint;

public class ReflectTest
{
    public static void main(String[] args) throws Exception 
    {
        String str = "abc";
        Class clz1 = String.class;
        Class clz2 = str.getClass();
        Class clz3 = Class.forName("java.lang.String");
        System.out.println(clz1 ==  clz2);
        System.out.println(clz1 ==  clz3);
        getConstructor();
        
        
        //成員變量的反射
        //1.首先我們先初始化一下ReflectPoint類的xy值,以便反射的時候獲取其值
        ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后續加載類
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析類
        System.out.println(xField.get(rp));
        Field yField = ReflectPoint.class.getField("y");
        System.out.println(yField.get(rp));
        
        //案例:changeStringValue
        System.out.println(rp);
        changeStringValue(rp);
        System.out.println(rp);
        
        //方法的反射
        Method methodCharAt = String.class.getMethod("charAt", int.class);
        System.out.println(methodCharAt.invoke(str, 1));
        //兼容JDK5.0之前的寫法
        System.out.println(methodCharAt.invoke(str, new Object[]{2}));
        
        //調用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 這種方式,因為有些場景我們的類是傳入的是你運行期才知道的
        Class clazz = Class.forName("study.javaenhance.TestArguments");
        Method method =  clazz.getMethod("main",String[].class);
        //因為main方法是靜態方法,所以第一個參數傳入的為null
        //method.invoke(null,new String[]{"1","2","3"});
        //這樣拆開后就是main方法想要的參數了
        method.invoke(null,new Object[]{new String[]{"1","2","3"}});
        //這樣就是一個對象傳入了,不需要拆了
        method.invoke(null,(Object)new String[]{"1","2","3"});
        
        //數組與Object的關系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
        
        Object aObj1 = a1; //因為a1 是一個int[] 類型的數組所以是object類型
        Object aObj2 = a4;//因為a4 是一個String[]類型的數組所以是Object類型
        //Object[] aObj3 = a1;// 報錯,因為a1 是一個int[]數組里面的元素都是int型,不是Object類型所以不可以如果改成包裝類就可以
        Object[] aObj4 = a3; //a3 是一個二維數組,那么里面的一維數組就是一個Object類型,所以可以匹配Object[]代表里面每個元素類型為Object
        Object[] aObj5 = a4; //String[] 里面的每一個元素都是String類型,也是Object類型 所以可以匹配.
        
        System.out.println(a1);
        System.out.println(a4);
        
        //Array應用
        System.out.println(Arrays.asList(a1));
        System.out.println(Arrays.asList(a4));    
        
        //寫一個方法,傳遞一個參數,判斷這個參數是一個數組,還是一個普通類型,如果是普通類型,我就直接把他打印出來。如果是數組,我們就遍歷后把值打印出來
        printObject(a1);
        
        printObject(a4);
        
        printObject("xyz");
        
    }

    
    private static void printObject(Object obj)
    {
        Class clazz = obj.getClass();
        if(clazz.isArray())
        {
            //如果是數組,利用數組的反射類Array類來進行相關操作
            int len = Array.getLength(obj);
            for(int i=0;i<len;i++){
                System.out.println(Array.get(obj, i));
            }
        }
        else
        {
            System.out.println(obj);
        }
        
    }


    private static void changeStringValue(Object rp) throws Exception
    {
        //思路:1.反射獲取要所有的成員變量  2.獲取成員變量的類型判斷是否為String類型 3.如果是則獲取其值 4.替換 5.回填
        Field[] fields = rp.getClass().getFields();
        for (Field field : fields) {
            if(field.getType() == String.class)
            {
                String oldValue = (String) field.get(rp);
                String newValue = oldValue.replace("b", "a");
                field.set(rp, newValue);
            }
        }
    }

    /**
     * 目前String 類型中有很多構造方法,我想要找到獲取構造方法為StringBuffer類型的一個
     * @throws Exception 
     * @throws SecurityException 
     */
    public static void getConstructor() throws SecurityException, Exception
    {
        String.class.getConstructor(StringBuffer.class);
        //那么上面的獲取到構造方法有什么用呢?
        /**
         * 通常,我們創建String 對象為 String abc = new String(new StringBuffer("abc")) 方式,
         * 下面我們通過反射來實現:
         *  1.加載類
         *  2.解析類
         *  
         */
        Constructor constructor = String.class.getConstructor(StringBuffer.class);
        String str =  (String) constructor.newInstance(new StringBuffer("abc"));
        System.out.println(str);
    }

}


class TestArguments{
    public static void main(String[] args){
        for(String arg : args){
            System.out.println(arg);
        }
    }
}

 

 

參考資料:

張孝祥Java基礎增強


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM