Java 基礎知識--反射

反射在Java編程中是很常用的功能,開發和閱讀源碼時,總能看到反射的身影。這裏要強調一下,Java的反射真的很簡單很簡單,很多人以爲Java的反射是一個很高深的知識點,一直不敢去觸碰,以至於成爲很多程序員的短板。接下來就一起來看看這個簡單了一逼的反射機制

Java的反射

反射概述

Java的反射是 在運行狀態中 ,對於任何一個,都能知道它的所有屬性方法;對於任何一個對象,都能調用它的所有屬性方法。這種能夠 動態獲取類的信息動態調用對象的方法 的特性就是Java的反射機制

簡單的說反射就是:動態獲取類的信息 和 動態調用對象的方法(或屬性)

反射的關鍵:獲取代表字節碼文件(.class文件)的Class對象

注: .java 文件通過編譯最終生成 .class 文件
注: Class對象代表着 .class(字節碼文件) [ 當然要先得到Class對象 ]

類加載簡單示例

先簡單瞭解一下類的加載過程

這裏寫圖片描述

Java編譯器會將 .java 文件編譯成 .class 文件

1.當程序執行 new User() 時,JVM會查找並加載 User.class 到內存中

2.Jvm 將 .class 加載到內存,自動創建一個Class對象。 Class對象由JVM創建,有且僅有一個,第二次 new User() 不再產生新的 Class 對象

3.一個類對應一個Class對象

反射的本質就是:得到 Class 對象後,反向獲取 User 類的各種信息。比如:構造方法,成員變量,方法等

接下來就看看如何使用 Class 對象獲取到 User 類的各種信息

Java反射基礎API使用

首先,創建一個 User 類

public class User {

}

這個類本身沒有什麼意義,用於演示反射示例

1.獲取Class對象的方式

1.通過 Object 的 getClass()

Object 類中存在 getClass() 方法,所有對象繼承自 Object 因此可以使用 Object 的 getClass() 方法獲取 Class 對象

2.通過任意類的“靜態”class屬性 例如:User.class

3.通過Class類的靜態方法 Class.forName(String name)

    public static void main(String args[]) {

        //1.使用Object 的 getClass() 方式獲取 Class 對象
        User user = new User();
        Class uClass1 = user.getClass();
        System.out.println("type1:" + uClass1.getName());

        //2.使用任意類的"靜態"class 屬性
        Class uClass2 = User.class;
        System.out.println("type2:" + uClass2.getName());

        //3.使用Class靜態方法 Class.
        try {
            Class uClass3 = Class.forName("demo.reflex.User");//參數是包括包名的完整路徑
            System.out.println("type3:" + uClass3.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

結果:

type1:demo.reflex.User
type2:demo.reflex.User
type3:demo.reflex.User

注意:運行期間,只有一個Class對象存在

以上幾種方式都可以獲取Class對象。第一個種能獲取到對象了,一般就不需要使用反射;第二種需要導入包,依賴性太強;第三種較爲常用,也比較符合反射的場景,根據一個完整的類名獲取Class對象

2.通過反射獲取類構造函數 Constructor

User 類

public class User {

    User(char ch) {
        System.out.println("默認 構造方法 ch:" + ch);
    }

    public User() {
        System.out.println("public 構造方法 無參");
    }

    public User(String name) {
        System.out.println("public 構造方法 name:" + name);
    }

    public User(String name, int age) {
        System.out.println("public 構造方法 name:" + name + " age:" + age);
    }

    protected User(int age) {
        System.out.println("protected 構造方法 age:" + age);
    }

    private User(boolean flag) {
        System.out.println("private 構造方法 flag:" + flag);
    }

}

User 類構造方法,包括了 public , private , protected , 默認 類型的構造方法

測試使用

    public static void main(String args[]) throws Exception {

        //獲取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------獲取所有公共的構造方法---------");
        Constructor[] consArray = cls.getConstructors();
        for (int i = 0; i < consArray.length; i++) {
            System.out.println(consArray[i]);
        }

        System.out.println("-------獲取所有的構造方法 包括:public,private,protected,默認類型---------");
        consArray = cls.getDeclaredConstructors();
        for (int i = 0; i < consArray.length; i++) {
            System.out.println(consArray[i]);
        }

        System.out.println("-------獲取 無參 構造方法並調用---------");
        Constructor cons = cls.getConstructor();
        System.out.println(cons);
        cons.newInstance();

        System.out.println("-------獲取 private 構造方法並調用---------");
        cons = cls.getDeclaredConstructor(boolean.class);
        System.out.println(cons);
        cons.setAccessible(true);//暴力訪問(忽略訪問修飾符)
        cons.newInstance(true);

    }

結果輸出

-------獲取所有公共的構造方法---------
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
-------獲取所有的構造方法 包括:public,private,protected,默認類型---------
private demo.reflex.User(boolean)
protected demo.reflex.User(int)
public demo.reflex.User(java.lang.String,int)
public demo.reflex.User(java.lang.String)
public demo.reflex.User()
demo.reflex.User(char)
-------獲取 無參 構造方法並調用---------
public demo.reflex.User()
public 構造方法 無參
-------獲取 private 構造方法並調用---------
private demo.reflex.User(boolean)
private 構造方法 flag:true

api解釋

1.獲取所有構造方法 返回符合要求的 列表

//獲取所有"公共的"構造方法  public
public Constructor<?>[] getConstructors()

//獲取所有的構造方法 包括: public,private,protected,默認類型 
public Constructor<?>[] getDeclaredConstructors() 

2.獲取單個構造方法 返回符合要求的 對象

//獲取"公共的"構造方法   
public Constructor<T> getConstructor(Class<?>... parameterTypes)

/**
 *獲取任意訪問類型的構造方法  
 *
 *parameterTypes:形參類型(記住是類型)  例:int.class ,String.class
 */
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

3.調用構造方法

/**
 *調用構造方法  
 *
 *initargs:構造方法參數
 */
public T newInstance(Object ... initargs)

newInstance() 方法屬於 Constructor 類,無參構造方法參數可以 null 或者 不填; 對於調用 private 私有構造方法需要調用 cons.setAccessible(true); 表示打破限制,強制訪問,否則出錯

3.通過反射獲取類成員變量 Field

User類

public class User {

    public User() {
    }

    /*成員變量*/
    private int age;
    public String name;
    protected String phone;
    boolean isVip;

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

測試使用

    public static void main(String args[]) throws Exception {

        //獲取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------獲取所有公共的成員變量---------");
        Field[] fieldArray = cls.getFields();
        for (int i = 0; i < fieldArray.length; i++) {
            System.out.println(fieldArray[i]);
        }

        System.out.println("-------獲取所有的成員變量 包括:public,private,protected,默認類型---------");
        fieldArray = cls.getDeclaredFields();
        for (int i = 0; i < fieldArray.length; i++) {
            System.out.println(fieldArray[i]);
        }

        System.out.println("-------獲取公共屬性---------");
        Field field = cls.getField("name");
        System.out.println(field);

        System.out.println("-------設置屬性並查看值---------");
        //根據構造函數 .newInstance() 獲取類對象
        Object object = cls.getConstructor().newInstance();
        field.set(object, "Ruffian");
        //驗證剛剛設置的屬性
        User user = (User) object;
        System.out.println("查看name屬性值:" + user.name);

        System.out.println("-------獲取私有屬性---------");
        field = cls.getDeclaredField("age");
        System.out.println(field);

        System.out.println("-------設置私有屬性並查看值---------");
        field.setAccessible(true);
        field.set(object, 18);
        //驗證剛剛設置的屬性
         System.out.println("查看age屬性值:" + user.toString());
    }

結果輸出:

-------獲取所有公共的成員變量---------
public java.lang.String demo.reflex.User.name
-------獲取所有的成員變量 包括:public,private,protected,默認類型---------
private int demo.reflex.User.age
public java.lang.String demo.reflex.User.name
protected java.lang.String demo.reflex.User.phone
boolean demo.reflex.User.isVip
-------獲取公共屬性---------
public java.lang.String demo.reflex.User.name
-------設置屬性並查看值---------
查看name屬性值:Ruffian
-------獲取私有屬性---------
private int demo.reflex.User.age
-------設置私有屬性並查看值---------
查看age屬性值:[User name=Ruffian, age=18]

api解釋

1.獲取所有屬性 返回符合要求的 列表

//獲取所有"公共的"屬性  public
public Constructor<?>[] getFields()

//獲取所有的屬性 包括: public,private,protected,默認類型 
public Constructor<?>[] getDeclaredFields() 

2.獲取單個屬性 返回符合要求的 對象

//獲取"公共的"屬性
public Field getField(String fieldName)

/**
 *獲取任意訪問類型的屬性
 *
 *fieldName:屬性名稱  例: name , age 
 */
public Field getDeclaredField(String fieldName)

3.設置屬性值

/**
 *1.obj:要設置的字段所在的對象;
 *2.value:要爲字段設置的值;
 */
public void set(Object obj,Object value)

field.set(object, "Ruffian"); 表示爲屬性設置值: user.name=Ruffian

第一個參數:要設置的字段所在的對象
第二個參數:要爲字段設置的值

至於上述示例中獲取屬性值的方式,是爲了方便校驗通過反射設置值是否成功,一般我們通過反射獲取方法,得到屬性值。下面看看如何通過反射獲取成員方法和調用

4.通過反射獲取成員方法 調用方法 Method

User類

public class User {

    public void showName(String name) {
        System.out.println("public showName()  參數name = " + name);
    }

    protected void show() {
        System.out.println("public show()");
    }

    void showDefault() {
        System.out.println(" showDefault() 默認無參方法");
    }

    private String getAge(int age) {
        System.out.println("private getAge() 返回值:字符串  參數:int age = " + age);
        return "age:" + age;
    }
}

使用示例

    public static void main(String args[]) throws Exception {

        //獲取Class
        Class cls = Class.forName("demo.reflex.User");

        System.out.println("-------獲取所有方法---------");
        //Method[] methodArray = cls.getMethods();
        Method[] methodArray = cls.getDeclaredMethods();
        for (int i = 0; i < methodArray.length; i++) {
            System.out.println(methodArray[i]);
        }

        System.out.println("-------調用任意訪問類型方法---------");
        //根據構造函數 .newInstance() 獲取類對象
        Object object = cls.getConstructor().newInstance();
        Method method = cls.getDeclaredMethod("getAge", int.class);
        method.setAccessible(true);
        Object result = method.invoke(object, 18);
        System.out.println("方法返回值:" + result.toString());
    }

結果輸出

-------獲取所有方法---------
private java.lang.String demo.reflex.User.getAge(int)
public void demo.reflex.User.showName(java.lang.String)
protected void demo.reflex.User.show()
void demo.reflex.User.showDefault()
-------調用任意訪問類型方法---------
private getAge() 返回值:字符串  參數:int age = 18
方法返回值:age:18

api解釋

1.獲取所有方法 返回符合要求的 列表

//獲取所有"公共的"方法  public
public Method[] getMethods()

//獲取所有的方法 包括: public,private,protected,默認類型 
public Method[] getDeclaredMethods()

2.獲取單個方法 返回符合要求的 對象

//獲取"公共的"方法
public Method getMethod(String name, Class<?>... parameterTypes)


/**
 *獲取任意訪問類型的方法
 *
 *name:方法名稱  例: getAge , showName 
 *parameterTypes:方法形參類型(記住是類型)  例:int.class ,String.class
 */
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

3.調用方法

/**
 *obj:要調用方法所在的對象
 *args:調用方法所需的參數
 */
public native Object invoke(Object obj, Object... args)

method.invoke(object, 18); 表示調用方法: User user=new User(); user.getAge(18);

第一個參數:

要調用方法所在的對象
如果調用方法是靜態的,那麼可以忽略指定的 obj 參數。該參數可以爲 null

第二個參數:

調用方法所需的參數
調用方法所需的形參個數爲 0,則所提供的 args 數組長度可以爲 0 或 null

4.Method 類的一點拓展

瞭解一個方法可以通過: 方法名稱,方法修飾符,返回類型,形參類型/個數

看下示例程序

    public static void main(String args[]) throws Exception {

        //獲取Class
        Class cls = Class.forName("demo.reflex.User");

        Method[] methodArray = cls.getDeclaredMethods();
        Method method;
        for (int i = 0; i < methodArray.length; i++) {
            method = methodArray[i];

            String name = method.getName();//方法名稱
            int modifiers = method.getModifiers();//方法修飾符
            Class returnType = method.getReturnType();//方法返回類型
            Class[] parameterTypes = method.getParameterTypes();//方法參數類型數組(形參類型)
            System.out.println("方法名:" + name + " 修飾符:" + Modifier.toString(modifiers) + " 返回值類型:" + returnType);

            //方法參數(形參)
            for (int j = 0; j < parameterTypes.length; j++) {
                System.out.println("形參" + j + ":" + parameterTypes[j]);
            }
            System.out.println("");
        }
    }

運行結果:

方法名:showName 修飾符:public 返回值類型:void
形參0:class java.lang.String

方法名:show 修飾符:protected 返回值類型:void

方法名:showDefault 修飾符: 返回值類型:void

方法名:getAge 修飾符:private 返回值類型:class java.lang.String
形參0:int
5.Java 反射的一點場景使用
5.1 反射結合配置文件使用

User類

public class User {

    public void show() {
        System.out.println("show方法被調用...");
    }
}

在 D 盤目錄下新建一個 pro.txt 文件

className = demo.reflex.User   //類名完整路徑
methodName = show              //方法名

示例代碼:

    public static void main(String args[]) throws Exception {

            //獲取Class
            Class cls = Class.forName(getValue("className"));
            //獲取方法
            Method method = cls.getMethod(getValue("methodName"));

            Object object = cls.getConstructor().newInstance();//獲取對象
            //調用方法
            method.invoke(object, null);
    }

    /***
     * 根據key獲取配置文件中 value
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static String getValue(String key) throws IOException {
        Properties pro = new Properties();//獲取配置文件的對象
        File file = new File("D:\\pro.txt");//獲取文件
        FileReader in = new FileReader(file);//獲取輸入流
        pro.load(in);//將流加載到配置文件對象中
        in.close();
        return pro.getProperty(key);//返回根據key獲取的value值
    }

輸出結果:

show方法被調用...
5.2 通過反射越過泛型檢查

還記得 method.invoke(Object obj, Object... args) 方法嗎? 第二個參數表示:調用方法所需參數。這是一個 Object 對象,那麼,嘿嘿嘿~~~

示例代碼:

    public static void main(String args[]) throws Exception {

        ArrayList<String> list = new ArrayList<>();
        list.add("AAA");
        list.add("BBB");

        /**
         * 獲取ArrayList的Class對象,反向的調用add()方法,添加數據
         */
        Class listClass = list.getClass(); //獲取Class對象
        //獲取add()方法
        Method m = listClass.getMethod("add", Object.class);
        //調用add()方法
        m.invoke(list, false);
        m.invoke(list, 100);

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

    }

輸出結果:

AAA
BBB
false
100

上述代碼,ArrayList 指定類型 String 如果直接 list.add(100) 則會包類型錯誤,通過反射調用 m.invoke(obj, obj); 傳入其他類型的對象,從而實現越過泛型的檢查。當然這只是一個例子,實際編碼中,還是需要嚴格按照規範去寫代碼

雖然,博文比較長,但是內容真的很簡單,純屬無腦式的API調用,起碼通過學習反射 API 的使用先了解反射的基礎。這也是爲什麼文章開篇說反射簡單了一逼,就是爲了鼓舞大家有勇氣面對這些知識點

由於本文都是API的簡單使用,也都參考網絡資料,就不一一附上連接了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章