天天看點

大牛:你真的懂反射嗎?反射是架構設計的靈魂

大牛:你真的懂反射嗎?

  • 反射是架構設計的靈魂
    • 一、反射的概述
    • 二、檢視Class類在java中的api詳解(1.7的API)
    • 三、反射的使用(這裡使用Student類做示範)

反射是架構設計的靈魂

一、反射的概述

JAVA反射機制是在運作狀态中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動态擷取的資訊以及動态調用對象的方法的功能稱為java語言的反射機制。

要想解剖一個類,必須先要擷取到該類的位元組碼檔案對象。而解剖使用的就是Class類中的方法.是以先要擷取到每一個位元組碼檔案對應的Class類型的對象.

反射就是把java類中的各種成分映射成一個個的Java對象

例如:一個類有:成員變量、方法、構造方法、包等等資訊,利用反射技術可以對一個類進行解剖,把個個組成部分映射成一個個對象。(其實:一個類中這些成員方法、構造方法、在加入類中都有一個類來描述)

如圖是類的正常加載過程:反射的原理在于Class對象。熟悉一下加載的時候:Class對象的由來是将class檔案讀入記憶體,并為之建立一個Class對象。

大牛:你真的懂反射嗎?反射是架構設計的靈魂

其中這個Class對象很特殊。我們先了解一下這個Class類

二、檢視Class類在java中的api詳解(1.7的API)

大牛:你真的懂反射嗎?反射是架構設計的靈魂

Class 類的執行個體表示正在運作的 Java 應用程式中的類和接口。也就是jvm中有N多的執行個體每個類都有該Class對象。(包括基本資料類型)

Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛拟機以及通過調用類加載器中的defineClass 方法自動構造的。也就是這不需要我們自己去處理建立,JVM已經幫我們建立好了。

沒有公共的構造方法,方法共有64個太多了。下面用到哪個就詳解哪個吧

大牛:你真的懂反射嗎?反射是架構設計的靈魂

三、反射的使用(這裡使用Student類做示範)

先寫一個Student類。

1、擷取Class對象的三種方式

1.1 Object ——> getClass();

1.2 任何資料類型(包括基本資料類型)都有一個“靜态”的class屬性

1.3 通過Class類的靜态方法:forName(String className)(常用)

其中1.1是因為Object類中的getClass方法、因為所有類都繼承Object類。進而調用Object類來擷取

大牛:你真的懂反射嗎?反射是架構設計的靈魂
/** 
 * 擷取Class對象的三種方式 
 * 1 Object ——> getClass(); 
 * 2 任何資料類型(包括基本資料類型)都有一個“靜态”的class屬性 
 * 3 通過Class類的靜态方法:forName(String  className)(常用) 
 * 
 */  
public class Fanshe {  
    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("fanshe.Student");//注意此字元串必須是真實路徑,就是帶包名的類路徑,包名.類名  
            System.out.println(stuClass3 == stuClass2);//判斷三種方式是否擷取的是同一個Class對象  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        }  

    }  
}
           

注意:在運作期間,一個類,隻有一個Class對象産生。

三種方式常用第三種,第一種對象都有了還要反射幹什麼。第二種需要導入類的包,依賴太強,不導包就抛編譯錯誤。一般都第三種,一個字元串可以傳入也可寫在配置檔案中等多種方法。

2、通過反射擷取構造方法并使用:

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);  
    }  

}  
           

共有6個構造方法;

測試類:

import java.lang.reflect.Constructor;  


/* 
 * 通過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("fanshe.Student");  


        //2.擷取所有公有構造方法  
        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(null);  
        //1>、因為是無參的構造方法是以類型是一個null,不寫也可以:這裡需要的是一個參數的類型,切記是類型  
        //2>、傳回的是描述這個無參構造函數的類對象。  

        System.out.println("con = " + con);  
        //調用構造方法  
        Object obj = con.newInstance();  
    //  System.out.println("obj = " + obj);  
    //  Student stu = (Student)obj;  

        System.out.println("******************擷取私有構造方法,并調用*******************************");  
        con = clazz.getDeclaredConstructor(char.class);  
        System.out.println(con);  
        //調用構造方法  
        con.setAccessible(true);//暴力通路(忽略掉通路修飾符)  
        obj = con.newInstance('男');  
    }  

}  
           

背景輸出:

所有公有構造方法***********

public fanshe.Student(java.lang.String,int)

public fanshe.Student(char)

public fanshe.Student()

所有的構造方法(包括:私有、受保護、預設、公有)***

private fanshe.Student(int)

protected fanshe.Student(boolean)

public fanshe.Student(java.lang.String,int)

public fanshe.Student(char)

public fanshe.Student()

fanshe.Student(java.lang.String)

擷取公有、無參的構造方法**************

con = public fanshe.Student()

調用了公有、無參構造方法執行了。。。

擷取私有構造方法,并調用*************

public fanshe.Student(char)

姓名:男

調用方法:

1、擷取構造方法:

public Constructor[] getConstructors():所有"公有的"構造方法

public Constructor[] getDeclaredConstructors():擷取所有的構造方法(包括私有、受保護、預設、公有)

1.1、擷取單個的方法,并調用:

public Constructor getConstructor(Class… parameterTypes):擷取單個的"公有的"構造方法:

public Constructor getDeclaredConstructor(Class… parameterTypes):擷取"某個構造方法"可以是私有的,或受保護、預設、公有;

調用構造方法:

Constructor–>newInstance(Object… initargs)

2、newInstance是 Constructor類的方法(管理構造函數的類)

api的解釋為:newInstance(Object… initargs)

使用此 Constructor 對象表示的構造方法來建立該構造方法的聲明類的新執行個體,并用指定的初始化參數初始化該執行個體。

它的傳回值是T類型,是以newInstance是建立了一個構造方法的聲明類的新執行個體對象。并為之調用

3、擷取成員變量并調用

student類:

public class Student {  
    public Student(){          
    }  
    //**********字段*************//  
    public String name;  
    protected int age;  
    char sex;  
    private String phoneNum;  
    @Override  
    public String toString() {  
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex  
                + ", phoneNum=" + phoneNum + "]";  
    }   
}
           

測試類:

import java.lang.reflect.Field;  
/* 
 * 擷取成員變量并調用: 
 *  
 * 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 class Fields {  

        public static void main(String[] args) throws Exception {  
            //1.擷取Class對象  
            Class stuClass = Class.forName("fanshe.field.Student");  
            //2.擷取字段  
            System.out.println("************擷取所有公有的字段********************");  
            Field[] fieldArray = stuClass.getFields();  
            for(Field f : fieldArray){  
                System.out.println(f);  
            }  
            System.out.println("************擷取所有的字段(包括私有、受保護、預設的)********************");  
            fieldArray = stuClass.getDeclaredFields();  
            for(Field f : fieldArray){  
                System.out.println(f);  
            }  
            System.out.println("*************擷取公有字段**并調用***********************************");  
            Field f = stuClass.getField("name");  
            System.out.println(f);  
            //擷取一個對象  
            Object obj = stuClass.getConstructor().newInstance();//産生Student對象--》Student stu = new Student();  
            //為字段設定值  
            f.set(obj, "劉德華");//為Student對象中的name屬性指派--》stu.name = "劉德華"  
            //驗證  
            Student stu = (Student)obj;  
            System.out.println("驗證姓名:" + stu.name);  


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

        }  
}
           

背景輸出:

擷取所有公有的字段********

public java.lang.String fanshe.field.Student.name

擷取所有的字段(包括私有、受保護、預設的)********

public java.lang.String fanshe.field.Student.name

protected int fanshe.field.Student.age

char fanshe.field.Student.sex

private java.lang.String fanshe.field.Student.phoneNum

擷取公有字段并調用********************

public java.lang.String fanshe.field.Student.name

驗證姓名:劉德華

擷取私有字段****并調用******************

private java.lang.String fanshe.field.Student.phoneNum

驗證電話:Student [name=劉德華, age=0, sex=

由此可見

調用字段時:需要傳遞兩個參數:

Object obj = stuClass.getConstructor().newInstance();//産生Student對象

Student stu = new Student();

//為字段設定值

f.set(obj, “劉德華”);//為Student對象中的name屬性指派–》stu.name = “劉德華”

第一個參數:要傳入設定的對象,第二個參數:要傳入實參

4、擷取成員方法并調用

student類:

public class Student {  
    //**************成員方法***************//  
    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";  
    }  
}  
           

測試類:

import java.lang.reflect.Method;  
/* 
 * 擷取成員方法并調用: 
 *  
 * 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 class MethodClass {  
    public static void main(String[] args) throws Exception {  
        //1.擷取Class對象  
        Class stuClass = Class.forName("fanshe.method.Student");  
        //2.擷取所有公有方法  
        System.out.println("***************擷取所有的”公有“方法*******************");  
        stuClass.getMethods();  
        Method[] methodArray = stuClass.getMethods();  
        for(Method m : methodArray){  
            System.out.println(m);  
        }  
        System.out.println("***************擷取所有的方法,包括私有的*******************");  
        methodArray = stuClass.getDeclaredMethods();  
        for(Method m : methodArray){  
            System.out.println(m);  
        }  
        System.out.println("***************擷取公有的show1()方法*******************");  
        Method m = stuClass.getMethod("show1", String.class);  
        System.out.println(m);  
        //執行個體化一個Student對象  
        Object obj = stuClass.getConstructor().newInstance();  
        m.invoke(obj, "劉德華");  
        System.out.println("***************擷取私有的show4()方法******************");  
        m = stuClass.getDeclaredMethod("show4", int.class);  
        System.out.println(m);  
        m.setAccessible(true);//解除私有限定  
        Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(擷取有反射),一個是實參  
        System.out.println("傳回值:" + result);  
    }  
}  
           

控制台輸出:

擷取所有的”公有“方法****

public void fanshe.method.Student.show1(java.lang.String)

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 final void java.lang.Object.wait() 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 fanshe.method.Student.show1(java.lang.String)

private java.lang.String fanshe.method.Student.show4(int)

protected void fanshe.method.Student.show2()

void fanshe.method.Student.show3()

擷取公有的show1()方法****

public void fanshe.method.Student.show1(java.lang.String)

調用了:公有的,String參數的show1(): s = 劉德華

擷取私有的show4()方法***

private java.lang.String fanshe.method.Student.show4(int)

調用了,私有的,并且有傳回值的,int參數的show4(): age = 20

傳回值:abcd

由此可見:

m = stuClass.getDeclaredMethod(“show4”, int.class);//調用制定方法(所有包括私有的),需要傳入兩個參數,第一個是調用的方法名稱,第二個是方法的形參類型,切記是類型。

System.out.println(m);

m.setAccessible(true);//解除私有限定

Object result = m.invoke(obj, 20);//需要兩個參數,一個是要調用的對象(擷取有反射),一個是實參System.out.println(“傳回值:” + result);//

5、反射main方法

student類:

public class Student {  

    public static void main(String[] args) {  
        System.out.println("main方法執行了。。。");  
    }  
}  
           

測試類:

import java.lang.reflect.Method;  

/** 
 * 擷取Student類的main方法、不要與目前的main方法搞混了 
 */  
public class Main {  

    public static void main(String[] args) {  
        try {  
            //1、擷取Student對象的位元組碼  
            Class clazz = Class.forName("fanshe.main.Student");  

            //2、擷取main方法  
             Method methodMain = clazz.getMethod("main", String[].class);//第一個參數:方法名稱,第二個參數:方法形參的類型,  
            //3、調用main方法  
            // methodMain.invoke(null, new String[]{"a","b","c"});  
             //第一個參數,對象類型,因為方法是static靜态的,是以為null可以,第二個參數是String數組,這裡要注意在jdk1.4時是數組,jdk1.5之後是可變參數  
             //這裡拆的時候将  new String[]{"a","b","c"} 拆成3個對象。。。是以需要将它強轉。  
             methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一  
            // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二  

        } catch (Exception e) {  
            e.printStackTrace();  
        }  


    }  
}
           

控制台輸出:

main方法執行了。。。

6、通過反射運作配置檔案内容

student類:

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

配置檔案以txt檔案為例子(pro.txt):

className = cn.fanshe.Student

methodName = show

測試類:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;

/* 
 * 我們利用反射和配置檔案,可以使:應用程式更新時,對源碼無需進行任何修改 
 * 我們隻需要将新類發送給用戶端,并修改配置檔案即可 
 */
public class Demo {
    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("pro.txt");//擷取輸入流  
        pro.load(in);//将流加載到配置檔案對象中  
        in.close();
        return pro.getProperty(key);//傳回根據key擷取的value值  
    }
}  
           

控制台輸出:

is show()

需求:

當我們更新這個系統時,不要Student類,而需要新寫一個Student2的類時,這時隻需要更改pro.txt的檔案内容就可以了。代碼就一點不用改動

要替換的student2類:

public class Student2 {  
    public void show2(){  
        System.out.println("is show2()");  
    }  
}  
           

配置檔案更改為:

className = cn.fanshe.Student2

methodName = show2

控制台輸出:

is show2();

7、通過反射越過泛型檢查

泛型用在編譯期,編譯過後泛型擦除(消失掉)。是以是可以通過反射越過泛型檢查的

測試類:

import java.lang.reflect.Method;
import java.util.ArrayList;

/* 
 * 通過反射越過泛型檢查 
 *  
 * 例如:有一個String泛型的集合,怎樣能向這個集合中添加一個Integer類型的值? 
 */
public class Demo {
    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

Java 反射 -超詳細講解(附源碼)