Java 反射

1.概述

1.1 什麼是反射

java反射機制是指在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法,對於任意一個對象,都能調用它的任意一個方法和屬性,這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。java

1.2 爲何要使用反射

正常的要實例化一個對象並調用sayHello方法spring

ClassA objA = new ClassA();
objA.sayHello();

經過反射去實例化一個對象並調用sayHello方法jvm

Class objA = Class.forName("com.spring.fanshe.Human");
Method method = objA.getMethod("sayHello");
method.invoke(objA.newInstance);

在source階段實際上兩者並沒有任何區別,反射也沒有體現出任何的優點,那麼任何一個java開發人員必然會問爲何要使用反射?ide

反射的重點在於runtime階段的獲取類信息和調用類方法,那麼當你的編碼過程當中有「部分信息是source階段不清晰,須要在runtime階段動態臨時加載」這種場景,反射就能夠派上用場了測試

咱們考慮幾個編碼場景:編碼

  1. 編碼階段不知道須要實例化的類名是哪一個,須要在runtime從配置文件中加載:
Class clazz = class.forName("xxx.xxx.xxx")
clazz.newInstance();
  1. 在runtime階段,須要臨時訪問類的某個私有屬性
ClassA objA = new ClassA();
Field xxx = objA.getClass().getDeclaredField("xxx")
xxx.setAccessible(true);

因此,反射的優勢在於「有些編碼需求在source階段沒法實現,只能在runtime階段經過反射實現」,而非「source階段正常編碼方式能解決的,反射的方式能解決的更好」,因此比較反射和正常編碼方式的優劣是沒有意義的,反射解決的是正常編碼沒法解決的編碼場景,若是正常編碼方式能夠解決的,強行使用反射反而是毫無心義的,編碼不是爲了show技巧。code

2. Class類的介紹

  • Class類的實例表示正在運行的Java應用程序中的類和接口,也就是JVM中有N多的實例,每一個類都有該Class對象(包括基本數據類型)
  • Class類沒有公共構造方法,Class對象是在加載類時由jvm以及經過調用類加載器中的defineClass方法自動構造的。也就是這不須要咱們本身去處理建立,JVM已經幫咱們建立好了。

若是咱們應用反射來在運行時獲得類的信息,根據類的那些信息作一些特定的操做,那麼,首先毫無疑問的就是獲得類的信息,在JDK中提供Class對象來保存類的信息,因此,反射的第一步就是獲得Class對象。在JDK中提供了三種方式獲得Class對象xml

這三種方式請看以下代碼:對象

public static void main(String[] args) {

        //第一種方式獲取Class對象
        Student stu1 = new Student();//這一new 產生一個Student對象,一個Class對象。
        Class stuClass = stu1.getClass();//獲取Class對象
        System.out.println(stuClass.getName());


        //第二種方式獲取Class對象
        Class stuClass2 = Student.class;
        System.out.println(stuClass == stuClass2);//判斷第一種方式獲取的Class對象和第二種方式獲取的是不是同一個


        //第三種方式獲取Class對象
        try {
            Class stuClass3 = Class.forName("com.spring.fanshe.Student");//注意此字符串必須是真實路徑,就是帶包名的類路徑,包名.類名
            System.out.println(stuClass3 == stuClass2);//判斷三種方式是否獲取的是同一個Class對象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

}

運行結果:繼承

調用了公有、無參構造方法執行了。。。
com.spring.fanshe.Student
true
true

注意:在運行期間,一個類,只有一個Class對象產生
上面獲得Class對象的方式中,經常使用第三種,第一種對象都有了還要反射干什麼,第二種須要導入類的包,依賴太強,不導包就拋編譯錯誤。通常都用第三種,一個字符串能夠傳入也能夠寫在配置文件中等多種方法。

3. 反射的使用

3.1 經過反射獲取構造方法並使用

首先咱們聲明一個Student類方便咱們進行介紹:

public class Student {

    //(默認的構造方法)
    Student(String str){
        System.out.println("(默認)的構造方法 s = " + str);
    }

    //無參構造方法
    public Student(){
        System.out.println("調用了公有、無參構造方法執行了。。。");
    }

    //有一個參數的構造方法
    public Student(char name){
        System.out.println("姓名:" + name);
    }

    //有多個參數的構造方法
    public Student(String name ,int age){
        System.out.println("姓名:"+name+"年齡:"+ age);
    }

    //受保護的構造方法
    protected Student(boolean n){
        System.out.println("受保護的構造方法 n = " + n);
    }

    //私有構造方法
    private Student(int age){
        System.out.println("私有的構造方法   年齡:"+ age);
    }
}

測試類

經過Class對象能夠獲取某個類中的:構造方法、成員變量、成員方法;並訪問成員;

/* 
 * 經過Class對象能夠獲取某個類中的:構造方法、成員變量、成員方法;並訪問成員; 
 *  
 * 1.獲取構造方法: 
 *      1).批量的方法: 
 *          public Constructor[] getConstructors():全部"公有的"構造方法 
            public Constructor[] getDeclaredConstructors():獲取全部的構造方法(包括私有、受保護、默認、公有) 
      
 *      2).獲取單個的方法,並調用: 
 *          public Constructor getConstructor(Class... parameterTypes):獲取單個的"公有的"構造方法: 
 *          public Constructor getDeclaredConstructor(Class... parameterTypes):獲取"某個構造方法"能夠是私有的,或受保護、默認、公有; 
 *       
 *          調用構造方法: 
 *          Constructor-->newInstance(Object... initargs) 
 */  
public class Constructors {
    public static void main(String[] args) throws Exception {
        //1.加載Class對象
        Class clazz = Class.forName("com.spring.fanshe.Student");


        System.out.println("**********************全部公有構造方法*********************************");
        Constructor[] conArray = clazz.getConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }


        System.out.println("************全部的構造方法(包括:私有、受保護、默認、公有)***************");
        conArray = clazz.getDeclaredConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }

        System.out.println("*****************獲取公有、無參的構造方法*******************************");
        Constructor con = clazz.getConstructor();
        System.out.println("con = " + con);
        //調用構造方法
        Object obj = con.newInstance();
        System.out.println("obj = " + obj);

        System.out.println("******************獲取私有構造方法,並調用*******************************");
        con = clazz.getDeclaredConstructor(int.class);
        System.out.println(con);
        //調用構造方法
        con.setAccessible(true);//暴力訪問(忽略掉訪問修飾符)
        obj = con.newInstance(25);
    }
}

運行結果以下:

**********************全部公有構造方法*********************************
public com.spring.fanshe.Student(java.lang.String,int)
public com.spring.fanshe.Student(char)
public com.spring.fanshe.Student()
************全部的構造方法(包括:私有、受保護、默認、公有)***************
private com.spring.fanshe.Student(int)
protected com.spring.fanshe.Student(boolean)
public com.spring.fanshe.Student(java.lang.String,int)
public com.spring.fanshe.Student(char)
public com.spring.fanshe.Student()
com.spring.fanshe.Student(java.lang.String)
*****************獲取公有、無參的構造方法*******************************
con = public com.spring.fanshe.Student()
調用了公有、無參構造方法執行了。。。
obj = com.spring.fanshe.Student@60e53b93
******************獲取私有構造方法,並調用*******************************
private com.spring.fanshe.Student(int)
私有的構造方法   年齡:25

3.2 獲取成員變量並調用

聲明一個Person類,定義了各類類型和修飾符的成員變量

public class Person {

    public String name;
    protected int age;
    char sex;
    private String phoneNum;

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", sex=" + sex
                + ", phoneNum=" + phoneNum + "]";
    }
}

測試類

/* 
 * 獲取成員變量並調用: 
 *  
 * 1.批量的 
 *      1).Field[] getFields():獲取全部的"公有字段" 
 *      2).Field[] getDeclaredFields():獲取全部字段,包括:私有、受保護、默認、公有; 
 * 2.獲取單個的: 
 *      1).public Field getField(String fieldName):獲取某個"公有的"字段; 
 *      2).public Field getDeclaredField(String fieldName):獲取某個字段(能夠是私有的) 
 *  
 *   設置字段的值: 
 *      Field --> public void set(Object obj,Object value): 
 *                  參數說明: 
 *                  1.obj:要設置的字段所在的對象; 
 *                  2.value:要爲字段設置的值; 
 *  
 */  
 
public static void main(String[] args) throws Exception {
        //1.獲取Class對象  
        Class personClass = Class.forName("com.spring.fanshe.Person");
        //2.獲取字段  
        System.out.println("************獲取全部公有的字段********************");
        Field[] fieldArray = personClass.getFields();
        for(Field f : fieldArray){
            System.out.println(f);
        }
        System.out.println("************獲取全部的字段(包括私有、受保護、默認的)********************");
        fieldArray = personClass.getDeclaredFields();
        for(Field f : fieldArray){
            System.out.println(f);
        }
        System.out.println("*************獲取公有字段**並調用***********************************");
        Field f = personClass.getField("name");
        System.out.println(f);
        //獲取一個對象  
        Object obj = personClass.getConstructor().newInstance();//產生Person對象--》Person person = new Person();  
        //爲字段設置值  
        f.set(obj, "劉德華");//爲Person對象中的name屬性賦值--》person.name = "劉德華"  
        //驗證  
        Person person = (Person)obj;
        System.out.println("驗證姓名:" + person.name);


        System.out.println("**************獲取私有字段****並調用********************************");
        f = personClass.getDeclaredField("phoneNum");
        System.out.println(f);
        f.setAccessible(true);//暴力反射,解除私有限定  
        f.set(obj, "18888889999");
        System.out.println("驗證電話:" + person);

    }

運行結果以下:

************獲取全部公有的字段********************
public java.lang.String com.spring.fanshe.Person.name
************獲取全部的字段(包括私有、受保護、默認的)********************
public java.lang.String com.spring.fanshe.Person.name
protected int com.spring.fanshe.Person.age
char com.spring.fanshe.Person.sex
private java.lang.String com.spring.fanshe.Person.phoneNum
*************獲取公有字段**並調用***********************************
public java.lang.String com.spring.fanshe.Person.name
驗證姓名:劉德華
**************獲取私有字段****並調用********************************
private java.lang.String com.spring.fanshe.Person.phoneNum
驗證電話:Person [name=劉德華, age=0, sex= , phoneNum=18888889999]

3.3 獲取成員方法並調用

咱們建立一個Human類,定義了3種成員方法,不一樣參數列表和不一樣的訪問修飾符

public class Human {

    public void show1(String s){
        System.out.println("調用了:公有的,String參數的show1(): s = " + s);
    }
    protected void show2(){
        System.out.println("調用了:受保護的,無參的show2()");
    }
    void show3(){
        System.out.println("調用了:默認的,無參的show3()");
    }
    private String show4(int age){
        System.out.println("調用了,私有的,而且有返回值的,int參數的show4(): age = " + age);
        return "abcd";
    }
}

測試類:

/* 
 * 獲取成員方法並調用: 
 *  
 * 1.批量的: 
 *      public Method[] getMethods():獲取全部"公有方法";(包含了父類的方法也包含Object類) 
 *      public Method[] getDeclaredMethods():獲取全部的成員方法,包括私有的(不包括繼承的) 
 * 2.獲取單個的: 
 *      public Method getMethod(String name,Class<?>... parameterTypes): 
 *                  參數: 
 *                      name : 方法名; 
 *                      Class ... : 形參的Class類型對象 
 *      public Method getDeclaredMethod(String name,Class<?>... parameterTypes) 
 *  
 *   調用方法: 
 *      Method --> public Object invoke(Object obj,Object... args): 
 *                  參數說明: 
 *                  obj : 要調用方法的對象; 
 *                  args:調用方式時所傳遞的實參; 
 */  
 
 
public static void main(String[] args) throws Exception {
        //獲取Class對象
        Class humClass = Class.forName("com.spring.fanshe.Human");


        System.out.println("***************獲取全部的」公有「方法*******************");
        humClass.getMethods();
        Method[] methodArray = humClass.getMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }


        System.out.println("***************獲取全部的方法,包括私有的*******************");
        methodArray = humClass.getDeclaredMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }


        System.out.println("***************獲取公有的show1()方法*******************");
        Method m = humClass.getMethod("show1", String.class);
        System.out.println(m);
        //實例化一個humdent對象  
        Object obj = humClass.newInstance();
        m.invoke(obj, "劉德華");


        System.out.println("***************獲取私有的show4()方法******************");
        m = humClass.getDeclaredMethod("show4", int.class);
        System.out.println(m);
        m.setAccessible(true);//解除私有限定  
        Object result = m.invoke(obj, 20);//須要兩個參數,一個是要調用的對象(獲取有反射),一個是實參  
        System.out.println("返回值:" + result);
    }

運行結果以下:

***************獲取全部的」公有「方法*******************
public void com.spring.fanshe.Human.show1(java.lang.String)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
***************獲取全部的方法,包括私有的*******************
public void com.spring.fanshe.Human.show1(java.lang.String)
private java.lang.String com.spring.fanshe.Human.show4(int)
void com.spring.fanshe.Human.show3()
protected void com.spring.fanshe.Human.show2()
***************獲取公有的show1()方法*******************
public void com.spring.fanshe.Human.show1(java.lang.String)
調用了:公有的,String參數的show1(): s = 劉德華
***************獲取私有的show4()方法******************
private java.lang.String com.spring.fanshe.Human.show4(int)
調用了,私有的,而且有返回值的,int參數的show4(): age = 20
返回值:abcd

3.4 經過反射運行配置文件內容

student類:

public class Book {  
    public void show(){  
        System.out.println("is show()");  
    }  
}

配置文件以txt文件爲例子(1.txt):

className = cn.fanshe.Student  
methodName = show

測試類:

/*
 * 咱們利用反射和配置文件,可使:應用程序更新時,對源碼無需進行任何修改
 * 咱們只須要將新類發送給客戶端,並修改配置文件便可
 */
public class BookClass {

    public static void main(String[] args) throws Exception {
        //經過反射獲取Class對象
        Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
        //2獲取show()方法
        Method m = stuClass.getMethod(getValue("methodName"));//show
        //3.調用show()方法
        m.invoke(stuClass.getConstructor().newInstance());

    }

    //此方法接收一個key,在配置文件中獲取相應的value
    public static String getValue(String key) throws IOException {
        Properties pro = new Properties();//獲取配置文件的對象
        FileReader in = new FileReader("F:\\project\\why-spring\\src\\main\\resources\\1.txt");//獲取輸入流
        pro.load(in);//將流加載到配置文件對象中
        in.close();
        return pro.getProperty(key);//返回根據key獲取的value值
    }

}

看到這裏有沒有想起Spring中配置Bean的XML文件:

<bean id="student" class="com.spring.fanshe.Book" />

3.5 經過反射越過泛型檢查

以下面方法所示,

public static void main(String[] args) throws Exception{
        ArrayList<String> strList = new ArrayList<>();
        strList.add("aaa");
        strList.add("bbb");
        
        //在聲明泛型爲String的類型的ArrayList中添加int類型
        strList.add(100);

        

        //遍歷集合
        for(Object obj : strList){
            System.out.println(obj);
        }
    }

運行結果:

Error:(16, 16) java: 對於add(int), 找不到合適的方法
    方法 java.util.Collection.add(java.lang.String)不適用
      (參數不匹配; int沒法轉換爲java.lang.String)

可是經過反射咱們就能夠繞過泛型檢查,將int類型的值100添加到泛型爲String的ArrayList 中,以下代碼所示:

public static void main(String[] args) throws Exception{
        ArrayList<String> strList = new ArrayList<>();
        strList.add("aaa");
        strList.add("bbb");
//        strList.add(100);
        //獲取ArrayList的Class對象,反向的調用add()方法,添加數據
        Class listClass = strList.getClass(); //獲得 strList 對象的字節碼 對象
        //獲取add()方法
        Method m = listClass.getMethod("add", Object.class);
        //調用add()方法
        m.invoke(strList, 100);

        //遍歷集合
        for(Object obj : strList){
            System.out.println(obj);
        }
    }

運行結果:

aaa
bbb
100

4. 反射的應用

在Spring的配置文件中,常常看到以下配置:

<bean id="courseDao" class="com.qcjy.learning.Dao.impl.CourseDaoImpl"></bean>

那麼經過這樣配置,Spring是怎麼幫咱們實例化對象,而且放到容器中去了了?對,就是經過反射!!!
下面是Spring經過配置進行實例化對象,並放到容器中的僞代碼:

//解析<bean .../>元素的id屬性獲得該字符串值爲「courseDao」  
String idStr = "courseDao";  
//解析<bean .../>元素的class屬性獲得該字符串值爲「com.qcjy.learning.Dao.impl.CourseDaoImpl」  
String classStr = "com.qcjy.learning.Dao.impl.CourseDaoImpl";  
//利用反射知識,經過classStr獲取Class類對象  
Class<?> cls = Class.forName(classStr);  
//實例化對象  
Object obj = cls.newInstance();  
//container表示Spring容器  
container.put(idStr, obj);

經過解析xml文件,獲取到id屬性和class屬性裏面的內容,利用反射原理獲取到配置裏面類的實例對象,存入到Spring的bean容器中。

當一個類裏面須要應用另外一類的對象時,Spring的配置以下所示:

<bean id="courseService" class="com.qcjy.learning.service.impl.CourseServiceImpl">  
     <!-- 控制調用setCourseDao()方法,將容器中的courseDao bean做爲傳入參數 -->  
     <property name="courseDao" ref="courseDao"></property>  
</bean>

咱們繼續用僞代碼的形式來模擬實現一下Spring底層處理原理:

//解析<property .../>元素的name屬性獲得該字符串值爲「courseDao」  
String nameStr = "courseDao";  
//解析<property .../>元素的ref屬性獲得該字符串值爲「courseDao」  
String refStr = "courseDao";  
//生成將要調用setter方法名  
String setterName = "set" + nameStr.substring(0, 1).toUpperCase()  
        + nameStr.substring(1);  
//獲取spring容器中名爲refStr的Bean,該Bean將會做爲傳入參數  
Object paramBean = container.get(refStr);  
//獲取setter方法的Method類,此處的cls是剛纔反射代碼獲得的Class對象  
Method setter = cls.getMethod(setterName, paramBean.getClass());  
//調用invoke()方法,此處的obj是剛纔反射代碼獲得的Object對象  
setter.invoke(obj, paramBean);

經過上面對Spring底層原理的分析,能夠發現,其實並不難,用到的都是反射機制,經過反射實例化對象,存入到Spring的bean容器中。

只要在代碼或配置文件中看到類的完整路徑(包.類),其底層原理基本上使用的就是Java的反射機制。

相關文章
相關標籤/搜索