Java反射知識點筆記

1. Class對象

類是程序的一部分,每個類都有一個Class對象,而反射所依賴的就是這個Class對象。

在JVM中,類都是動態加載的,當程序創建第一個對類的靜態成員的引用時,就會加載這個類,這個可以證明構造函數也是類的靜態方法,即使構造方法沒有使用static關鍵字。

類加載器首先檢查在這個類的Class對象是否已經加載,如果尚未加載,默認的類加載器就會根據類名查找 .class 文件,這個類的所有對象都是依據這個類的 .class對象 來創建的。下面舉個例子:

package com.ga;
class Candy{
    static{
        System.out.println("Loading Candy");
    }
}
class Gum{
    static{
        System.out.println("Loading Gum");
    }
}
class Cookie{
    static{
        System.out.println("Loading Cookie");
    }
}
public class SweetShop {
    public static void main(String[] args) {
        System.out.println("inside main");
        new Candy();
        System.out.println("After creating Candy");
        try{
            Class.forName("Gum");           //獲取Class對象的引用的一種方法
        }catch (ClassNotFoundException e){
            System.out.println("找不到Gum");;
        }
        System.out.println("After Class.forName(\"Gum\")");
        new Cookie();
        System.out.println("After creating Cookie");
    }
}
輸出結果:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie

上面就用了兩種方式來加載類,第一種直接 new 某個類,第二種就是調用靜態函數 forName() ,根據類名進行加載。

常用的獲取Class對象的方法有:

Class.forName();         //這裏調用的是Class類的靜態函數
class.getClass();        //這裏是調用對象的 getClass() 方法
Class.getInterfaces();   //查詢接口,第一個 Class 是某個類的類名
Class.getSuperclass();   //查詢基類,第一個 Class 是某個類的類名
Class.class;             //類字面常量,第一個 Class 是某個類的類名

注:推薦使用類字面常量。如果使用 forName() 或者 getClass() ,有可能會因爲沒找到類(類名打錯)而在運行時報錯,使用類字面常量的話,他在編寫代碼時就受到檢查,如果沒有導入這個類,就會提示錯誤。

2. 反射

反射就是在程序運行時,獲取某個類的Class對象,並根據這個Class對象新建一個實例對象,或者獲得這個類所擁有的方法,屬性,來實現一些通用的方法。大部分框架的實現就是使用了反射。

舉個例子,獲取User的屬性和方法:

class User{
    private String name;
    private String password;

	public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public User(String name){
        this.name = name;
    }

    public User() {
    }
    //get/set省略

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
    
    public static void main(String[] args) {
        Field[] fields = User.class.getDeclaredFields();       //獲取所有字段
        Method[] methods = User.class.getDeclaredMethods();    //獲取所有方法
        for (Field field: fields) {
            System.out.println(field.toString());
        }
        for (Method method: methods) {
            System.out.println(method.toString());
        }
    }
}
輸出結果:
private java.lang.String com.qianfeng.User.name
private java.lang.String com.qianfeng.User.password
public java.lang.String com.qianfeng.User.getName()
public void com.qianfeng.User.setName(java.lang.String)
public java.lang.String com.qianfeng.User.getPassword()
public void com.qianfeng.User.setPassword(java.lang.String)

注:getConstructors(),getDeclaredFields(),getDeclaredMethods()等返回數組的方法,數組內容的順序是隨機的。(如要保證順序,可參照Java反射,順序輸出類中的方法

接下來列舉一些常用方法(有 Declared 這個詞的方法,獲得到任意訪問權限的屬性或方法,沒有的話只能訪問公有即 public 的屬性或方法):

獲取構造器

public static void main(String[] args) {
        try {
        	//獲取User類中的一個公有構造方法,爲空就是無參構造器
            Constructor constructor1 = User.class.getConstructor();
            //根據參數類型的類對象獲取對應的一個構造方法,這個方法就會獲取只有一個參數,且參數類型爲String類型的構造器
            Constructor constructor2 = User.class.getConstructor(String.class);
            //獲取User類中所有的公有構造器
            Constructor[] constructor3 = User.class.getConstructors();
            //獲取User類中的任意權限的一個構造器,用法同上面的獲取公有構造器的方法
            Constructor constructor4 = User.class.getDeclaredConstructor();
            //獲取User類中的所有構造器
            Constructor[] constructor5 = User.class.getDeclaredConstructors();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

獲取成員變量

public static void main(String[] args) {
        try {
			//根據屬性名獲取對應的公有屬性
            Field field = User.class.getField("name");    
            //獲取所有公有屬性   
            Field[] fields = User.class.getFields();
            //根據屬性名獲取對應的任意權限的屬性
            Field field1 = User.class.getDeclaredField("password");
            //獲取所有權限的屬性
            Field[] fields1 = User.class.getDeclaredFields();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

獲取成員方法

先給User添加兩個方法

	public User Create(String name, String password){
        return new User(name, password);
    }
    public User Create(String name){
        return new User(name);
    }
public static void main(String[] args) {
        try {
        	//根據函數名和參數類型獲取對應的公有成員方法
			Method method1 = User.class.getMethod("Create", String.class);
			//獲取所有的公有成員方法
            Method[] method2 = User.class.getMethods();
            //根據函數名和參數類型獲取對應的所有權限的成員方法
            Method method3 = User.class.getDeclaredMethod("Create", String.class, String.class);
            //獲取所有權限的成員方法
            Method[] method4 = User.class.getDeclaredMethods();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

使用技巧

獲取了類的屬性和方法後,就可以根據這些來修改該類的實例對象的屬性的值,或者調用方法,比如:

public static void main(String[] args) {
        User user = null;
        try {
            //通過反射創建實例,
            user = User.class.newInstance();
            //通過反射獲取 name 字段,並將對象 user 中的 name 字段的值設爲 “user”
            User.class.getField("name").setAccessible(true);    ////解除私有限定
            User.class.getField("name").set(user, "user");
            //通過反射獲取 password 字段,並將對象 user 中的 password 字段的值設爲 “password”
            User.class.getField("password").setAccessible(true);
            User.class.getField("password").set(user, "password");
            
            System.out.println(user);
            //通過反射獲取只有一個參數,且參數類型爲 String 類型的 setName 方法,通過invoke調用,invoke()中,第一個參數爲要調用該方法的對象實例,第二個爲參數的值
            Object object = user;
            User.class.getDeclaredMethod("setName", String.class).invoke(object, "user1");
            User.class.getDeclaredMethod("setPassword", String.class).invoke(object, "password1");
            System.out.println(user);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
輸出結果
User{name='user', password='password'}
User{name='user1', password='password1'}

由於反射類中的 Field , Method 和 Constructor 繼承自 AccessibleObject ,因此,通過在這些類上調用 setAccessible() 方法,可以修改訪問權限,從而實現對這些字段的操作。

也可以用下面的方式解除操作權限,第一個參數爲字段的對象,第二個爲權限:

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