Java反射機制

xy4122 8年前發布 | 25K 次閱讀 反射 Java開發 Java

前言

今天介紹下Java的反射機制,以前我們獲取一個類的實例都是使用new一個實例出來。那樣太low了,今天跟我一起來學習學習一種更加高大上的方式來實現。

正文

Java反射機制定義

Java反射機制是指在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
用一句話總結就是反射可以實現在運行時可以知道任意一個類的屬性和方法。

反射機制的優點與缺點

為什么要用反射機制?直接創建對象不就可以了嗎,這就涉及到了動態與靜態的概念

  • 靜態編譯:在編譯時確定類型,綁定對象,即通過。
  • 動態編譯:運行時確定類型,綁定對象。動態編譯最大限度發揮了java的靈活性,體現了多態的應用,有以降低類之間的藕合性。

  • 優點
    可以實現動態創建對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟件,不可能一次就把把它設計的很完美,當這個程序編譯后,發布了,當發現需要更新某些功能時,我們不可能要用戶把以前的卸載,再重新安裝新的版本,假如這樣的話,這個軟件肯定是沒有多少人用的。采用靜態的話,需要把整個程序重新編譯一次才可以實現功能的更新,而采用反射機制的話,它就可以不用卸載,只需要在運行時才動態的創建和編譯,就可以實現該功能。

  • 缺點
    對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執行相同的操作。

理解Class類和類類型

想要了解反射首先理解一下Class類,它是反射實現的基礎。
類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)
對于普通的對象,我們一般都會這樣創建和表示:

Code code1 = new Code();

上面說了,所有的類都是Class的對象,那么如何表示呢,可不可以通過如下方式呢:

Class c = new Class();

但是我們查看Class的源碼時,是這樣寫的:

private  Class(ClassLoader loader) { 
    classLoader = loader; 
}

可以看到構造器是私有的,只有JVM可以創建Class的對象,因此不可以像普通類一樣new一個Class對象,雖然我們不能new一個Class對象,但是卻可以通過已有的類得到一個Class對象,共有三種方式,如下:

Class c1 = Code.class;
這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是通過獲取類的靜態成員變量class得到的
Class c2 = code1.getClass();
code1是Code的一個對象,這種方式是通過一個類的對象的getClass()方法獲得的
Class c3 = Class.forName("com.trigl.reflect.Code");
這種方法是Class類調用forName方法,通過一個類的全量限定名獲得

這里,c1、c2、c3都是Class的對象,他們是完全一樣的,而且有個學名,叫做Code的類類型(class type)。
這里就讓人奇怪了,前面不是說Code是Class的對象嗎,而c1、c2、c3也是Class的對象,那么Code和c1、c2、c3不就一樣了嗎?為什么還叫Code什么類類型?這里不要糾結于它們是否相同,只要理解類類型是干什么的就好了,顧名思義,類類型就是類的類型,也就是描述一個類是什么,都有哪些東西,所以我們可以通過類類型知道一個類的屬性和方法,并且可以調用一個類的屬性和方法,這就是反射的基礎。

舉個簡單例子代碼:

public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一種:Class c1 = Code.class;
        Class class1=ReflectDemo.class;
        System.out.println(class1.getName());

    //第二種:Class c2 = code1.getClass();
    ReflectDemo demo2= new ReflectDemo();
    Class c2 = demo2.getClass();
    System.out.println(c2.getName());

    //第三種:Class c3 = Class.forName("com.trigl.reflect.Code");
    Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
    System.out.println(class3.getName());
}

}</code></pre>

執行結果:

com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

Java反射相關操作

前面我們知道了怎么獲取Class,那么我們可以通過這個Class干什么呢?
總結如下:

  • 獲取成員方法Method
  • 獲取成員變量Field
  • 獲取構造函數Constructor

下面來具體介紹

獲取成員方法信息

單獨獲取某一個方法是通過Class類的以下方法獲得的:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到該類所有的方法,不包括父類的
public Method getMethod(String name, Class<?>... parameterTypes) // 得到該類所有的public方法,包括父類的

兩個參數分別是方法名和方法參數類的類類型列表。
例如類A有如下一個方法:

public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"歲");
    }

現在知道A有一個對象a,那么就可以通過:

Class c = Class.forName("com.tengj.reflect.Person");  //先生成class
Object o = c.newInstance();                           //newInstance可以初始化一個實例
Method method = c.getMethod("fun", String.class, int.class);//獲取方法
method.invoke(o, "tengj", 10);                              //通過invoke調用該方法,參數第一個為實例對象,后面為具體參數值

完整代碼如下:

public class Person {
    private String name;
    private int age;
    private String msg="hello wrold";
 public String getName() {
        return name;
  }

public void setName(String name) {
    this.name = name;

}

public int getAge() {
    return age;

}

public void setAge(int age) {
    this.age = age;

}

public Person() {
}

private Person(String name) {
    this.name = name;

System.out.println(name); }

public void fun() {
    System.out.println("fun");

}

public void fun(String name,int age) {
    System.out.println("我叫"+name+",今年"+age+"歲");

} }

public class ReflectDemo { public static void main(String[] args){ try { Class c = Class.forName("com.tengj.reflect.Person"); Object o = c.newInstance(); Method method = c.getMethod("fun", String.class, int.class); method.invoke(o, "tengj", 10); } catch (Exception e) { e.printStackTrace(); } } }</code></pre>

執行結果:

我叫tengj,今年10歲

怎樣,是不是感覺很厲害,我們只要知道這個類的路徑全稱就能玩弄它于鼓掌之間。

有時候我們想獲取類中所有成員方法的信息,要怎么辦。可以通過以下幾步來實現:
1.獲取所有方法的數組:

Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 得到該類所有的方法,不包括父類的
或者:
Method[] methods = c.getMethods();// 得到該類所有的public方法,包括父類的

2.然后循環這個數組就得到每個方法了:

for (Method method : methods)

完整代碼如下:
person類跟上面一樣,這里以及后面就不貼出來了,只貼關鍵代碼

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Method[] methods = c.getDeclaredMethods();
            for(Method m:methods){
                String  methodName= m.getName();
                System.out.println(methodName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

getName
setName
setAge
fun
fun
getAge

這里如果把c.getDeclaredMethods();改成c.getMethods();執行結果如下,多了很多方法,以為把Object里面的方法也打印出來了,因為Object是所有類的父類:

getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

獲取成員變量信息

想一想成員變量中都包括什么:成員變量類型+成員變量名
類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象,所以我們通過java.lang.reflect.Field里面封裝的方法來獲取這些信息。

單獨獲取某個成員變量,通過Class類的以下方法實現:

public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變量,不包括其父類的變量
public Field getField(String name) // 獲得該類自所有的public成員變量,包括其父類變量

參數是成員變量的名字。
例如一個類A有如下成員變量:

private int n;

如果A有一個對象a,那么就可以這樣得到其成員變量:

Class c = a.getClass();
Field field = c.getDeclaredField("n");

完整代碼如下:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取成員變量
            Field field = c.getDeclaredField("msg"); //因為msg變量是private的,所以不能用getField方法
            Object o = c.newInstance();
            field.setAccessible(true);//設置是否允許訪問,因為該變量是private的,所以要手動設置允許訪問,如果msg是public的就不需要這行了。
            Object msg = field.get(o);
            System.out.println(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

hello wrold

同樣,如果想要獲取所有成員變量的信息,可以通過以下幾步
1.獲取所有成員變量的數組:

Field[] fields = c.getDeclaredFields();

2.遍歷變量數組,獲得某個成員變量field

for (Field field : fields)

完整代碼:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Field[] fields = c.getDeclaredFields();
            for(Field field :fields){
                System.out.println(field.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

name
age
msg

獲取構造函數

最后再想一想構造函數中都包括什么:構造函數參數
同上,類的成構造函數也是一個對象,它是java.lang.reflect.Constructor的一個對象,所以我們通過java.lang.reflect.Constructor里面封裝的方法來獲取這些信息。

單獨獲取某個構造函數,通過Class類的以下方法實現:

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  獲得該類所有的構造器,不包括其父類的構造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 獲得該類所以public構造器,包括父類

這個參數為構造函數參數類的類類型列表。
例如類A有如下一個構造函數:

public A(String a, int b) {
    // code body
}

那么就可以通過:

Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

來獲取這個構造函數。

完整代碼:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //獲取構造函數
            Constructor constructor = c.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);//設置是否允許訪問,因為該構造器是private的,所以要手動設置允許訪問,如果構造器是public的就不需要這行了。
            constructor.newInstance("tengj");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

tengj

注意:Class的newInstance方法,只能創建只包含無參數的構造函數的類,如果某類只有帶參數的構造函數,那么就要使用另外一種方式:fromClass.getDeclaredConstructor(String.class).newInstance("tengj");

獲取所有的構造函數,可以通過以下步驟實現:
1.獲取該類的所有構造函數,放在一個數組中:

Constructor[] constructors = c.getDeclaredConstructors();

2.遍歷構造函數數組,獲得某個構造函數constructor:

for (Constructor constructor : constructors)

完整代碼:

public class ReflectDemo {
    public static void main(String[] args){
            Constructor[] constructors = c.getDeclaredConstructors();
            for(Constructor constructor:constructors){
                System.out.println(constructor);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執行結果:

public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)

通過反射了解集合泛型的本質

首先下結論:

Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。

下面通過一個實例來驗證:

/**

  • 集合泛型的本質
  • @description
  • @author Trigl
  • @date 2016年4月2日上午2:54:11 */ public class GenericEssence { public static void main(String[] args) {
     List list1 = new ArrayList(); // 沒有泛型 
     List<String> list2 = new ArrayList<String>(); // 有泛型
    /*
     * 1.首先觀察正常添加元素方式,在編譯器檢查泛型,
     * 這個時候如果list2添加int類型會報錯
     */
    list2.add("hello");

// list2.add(20); // 報錯!list2有泛型限制,只能添加String,添加int報錯 System.out.println("list2的長度是:" + list2.size()); // 此時list2長度為1

    /*
     * 2.然后通過反射添加元素方式,在運行期動態加載類,首先得到list1和list2
     * 的類類型相同,然后再通過方法反射繞過編譯器來調用add方法,看能否插入int
     * 型的元素
     */
    Class c1 = list1.getClass();
    Class c2 = list2.getClass();
    System.out.println(c1 == c2); // 結果:true,說明類類型完全相同

    // 驗證:我們可以通過方法的反射來給list2添加元素,這樣可以繞過編譯檢查
    try {
        Method m = c2.getMethod("add", Object.class); // 通過方法反射得到add方法
        m.invoke(list2, 20); // 給list2添加一個int型的,上面顯示在編譯器是會報錯的
        System.out.println("list2的長度是:" + list2.size()); // 結果:2,說明list2長度增加了,并沒有泛型檢查
    } catch (Exception e) {
        e.printStackTrace();
    }

    /*
     * 綜上可以看出,在編譯器的時候,泛型會限制集合內元素類型保持一致,但是編譯器結束進入
     * 運行期以后,泛型就不再起作用了,即使是不同類型的元素也可以插入集合。
     */
}

}</code></pre>

執行結果:

list2的長度是:1
true
list2的長度是:2

總結

到此,Java反射機制入門的差不多了,我是復習SpringMVC里面IOC/DI的時候,底層原理是通過Java反射來實現的,希望這篇筆記也對你有用。

參考

Java反射機制深入詳解
Java反射入門
Java反射機制
java反射詳解
Java 反射機制淺析
反射機制的理解及其用途

整理的思維導圖

個人整理的Java反射機制的思維導圖,導出的圖片無法查看備注的一些信息,所以需要源文件的童鞋可以關注我個人主頁上的公眾號,回復反射機制即可獲取源文件。

Java反射機制

一直覺得自己寫的不是技術,而是情懷,一篇篇文章是自己這一路走來的痕跡。靠專業技能的成功是最具可復制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識的蒙塵,希望我能幫你理清知識的脈絡,希望未來技術之巔上有你也有我。


 

文/嘟嘟MD(簡書作者)
原文鏈接:http://www.jianshu.com/p/1a60d55a94cd
著作權歸作者所有,轉載請聯系作者獲得授權,并標注“簡書作者”。

 本文由用戶 xy4122 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!