Java反射機制基礎詳解

1、什麼是Java反射機制?

在程序運行中動態地獲取類的相關屬性,同時調用對象的方法和獲取屬性,這種機制被稱之為Java反射機制

下面給出一個反射的簡單例子:

import lombok.Data;

@Data
public class User {

    public String username;
    private String password;

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
public static void reflectionSimpleExample() throws Exception{
    User user = new User();
    System.out.println(user.toString());
	// 獲取對應類的class
    Class<?> cls = Class.forName("com.example.core.example.reflection.domain.User");
    Object obj = cls.newInstance();
    System.out.println(obj);
	// 獲取成員變量,註意要是public的
    Field field = cls.getField("username");
    System.out.println(field.get(obj));

}

2、反射機制原理

Java反射是Java實現動態語言的關鍵,也就是通過反射實現類動態加載

靜態加載: 在編譯時加載相關的類,如果找不到類就會報錯,依賴性比較強動態加載:在運行時加載需要的類,在項目跑起來之後,調用才會報錯,降低瞭依賴性

例子:靜態加載,如下代碼,如果找不到類的情況,代碼編譯都不通過

User user = new User();

而動態加載,就是反射的情況,是可以先編譯通過的,然後在調用代碼時候,也就是運行時才會報錯

 Class<?> cls = Class.forName("com.example.core.example.reflection.User");
Object obj = cls.newInstance();

Exception in thread “main” java.lang.ClassNotFoundException: com.example.core.example.reflection.User

java中的反射允許程序在執行期借助jdk中Reflection API來獲取類的內部信息,比如成員變量、成員方法、構造方法等等,並能操作類的屬性和方法

java中反射的實現和jvm和類加載機制有一定的關系,加載好類之後,在jvm的堆中會產生一個class類型的對象,這個class類包括瞭類的完整結構信息,通過這個class對象就可以獲取到類的結構信息,所以形象地稱之為java反射

在這裡插入圖片描述

3、Class類介紹

3.1、Class類基本介紹

然後這個Class類是什麼?看下uml類圖:

在這裡插入圖片描述

Class也是類,因此也繼承Object類

Class不是直接new出來的,而是經過系統創建的

User user = new User();

打個斷點,debug進行看看源碼,可以看到傳統的直接new一個對象也是通過類加載器loadClass拿到的

在這裡插入圖片描述

java反射方式,通用通過調試看看:

 Class<?> cls = Class.forName("com.example.core.example.reflection.domain.User");

同樣本質也是通過ClassLoad再通過類的全類名

在這裡插入圖片描述

3.2、Class類對象的獲取方法 Class.forname()

Class<?> cls = Class.forName("com.example.core.example.reflection.domain.User");

應用場景:多用於讀取類全路徑,加載類

具體類.class
已經知道具體類的情況,通過具體類的class屬性獲取

Class u = User.class;

應用場景:多用於用於參數傳遞

對象.getClass
已經創建好對象的情況,直接通過對象實例獲取class對象

Class cls = user.getClass();

應用場景:通過創建好的對象,獲取Class對象

ClassLoader獲取

ClassLoader cl = user.getClass().getClassLoader();
Class cls = cl.loadClass("com.example.core.example.reflection.domain.User");

基本數據類型

Class cls = int.class;

包裝類

基本數據類型對應的包裝類可以通過.TYPE得到Class類對象

Class cls = Integer.TYPE;

3.3 、可以獲取Class對象的類型

1、外部類,成員內部類,靜態內部類,局部內部類,匿名內部類

2、interface:接口

3、數組

4、enum:枚舉

5、annotation:註解

6、基本數據類型

7、void8、Class… ,等等

import com.example.core.example.reflection.domain.User;
import lombok.ToString;

import java.util.List;

public class GetClassObjectExample {

    public static void main(String[] args) {
        // 外部類
        Class<User> cls1 = User.class;
        // 接口
        Class<List> cls2 = List.class;
        // 數組
        Class<Integer[]> cls3 = Integer[].class;
        // 二維數組
        Class<String[][]> cls4 = String[][].class;
        // 註解
        Class<lombok.ToString> cls5 = ToString.class;

        // 枚舉
        Class<Thread.State> cls6 = Thread.State.class;
        // 基本數據類型
        Class<Long> cls7 = Long.class;
        // void 數據類型
        Class<Void> cls8 = Void.class;
        // Class
        Class<Class> cls9 = Class.class;

        System.out.println(cls1);
        System.out.println(cls2);
        System.out.println(cls3);
        System.out.println(cls4);
        System.out.println(cls5);
        System.out.println(cls6);
        System.out.println(cls7);
        System.out.println(cls8);
        System.out.println(cls9);
    }
}

4、java反射的作用?

可以通過外部類的全路徑名創建對象,並使用這些類可以枚舉出類的全部成員,包括構造函數、屬性、方法利用反射 API 訪問類的私有成員

5、反射API主要類

1、java.lang.Class:代表一個類,表示某個類在jvm堆中的對象

2、 java.lang.reflect.Method:代表類的方法

3、 java.lang.reflect.Field:代表類的成員變量

4、 java.lang.reflect.Constructor:代表類額構造方法

6、Java反射的優缺點

優點:使用Java反射可以靈活動態地創建和使用對象,反射是框架的底層支撐缺點:使用Java反射,基本就是解釋執行的,對執行速度是有影響的

7、反射調用的優化方法

前面介紹瞭Java反射雖然很靈活,但是缺點就是調用時候比較慢,相對直接new對象來說,情況是怎麼樣的?下面通過例子進行試驗:

import com.example.core.example.reflection.domain.User;

import java.lang.reflect.Method;

public class TestReflectionExample {

    private static final Integer TOTAL_COUNT = 1000000;

    public static void main(String[] args) throws Exception{
        test0();
        test1();
        test2();
    }

    public static void test0() {
        long start = System.currentTimeMillis();
        User user = new User();
        for (int i = 0 ; i < TOTAL_COUNT; i++) {
            user.hello();
        }
        System.out.println(String.format("傳統調用方法執行時間:%d" , System.currentTimeMillis() - start));
    }

    public static void test1() throws Exception{
        long start = System.currentTimeMillis();
        Class<?> cls = Class.forName("com.example.core.example.reflection.domain.User");
        Object obj = cls.newInstance();
        Method hello = cls.getMethod("hello");
        for (int i = 0 ; i < TOTAL_COUNT; i++) {
            hello.invoke(obj);
        }
        System.out.println(String.format("反射方法調用執行時間:%d" , System.currentTimeMillis() - start));
    }

    public static void test2() throws Exception{
        long start = System.currentTimeMillis();
        Class<?> cls = Class.forName("com.example.core.example.reflection.domain.User");
        Object obj = cls.newInstance();
        Method hello = cls.getMethod("hello");
        // 關鍵,取消調用反射方法時的安全檢查
        hello.setAccessible(true);
        for (int i = 0 ; i < TOTAL_COUNT; i++) {
            hello.invoke(obj);
        }
        System.out.println(String.format("優化後的反射方法調用執行時間:%d" , System.currentTimeMillis() - start));
    }
}

傳統調用方法執行時間:19
反射方法調用執行時間:112
優化後的反射方法調用執行時間:50

8、反射的基本使用例子

java.lang.Class類

方法名 作用
getName 獲取全類名
getSimpleName 獲取簡單類名
getFields 獲取所有public修飾的屬性、包括本類以及父類的
getDeclaredFields 獲取本類中所有的屬性
getMethods 獲取所有的public修飾的方法,包括本類以及父類的
getDeclaredMethod 獲取本類中所有方法
getConstructors 獲取所有public修飾的構造器,隻有本類
getDeclaredConstructors 獲取本類中所有構造器
getPackage Package形式返回 包信息
getSuperClass Class形式返回父信息
getInterfaces Class[]形式返回父接口信息
getAnnotations Annotation[]形式返回註解信息
String allClassName = "com.example.core.example.reflection.domain.User";
// 通過全類名獲取class對象
Class<?> cls = Class.forName(allClassName);
System.out.println(cls);
// 通過對象獲取class
System.out.println(cls.getClass());
// 獲取全類名
System.out.println(cls.getName());
// 獲取包名
System.out.println(cls.getClass().getPackage().getName());
// 獲取對象實例
Object obj = cls.newInstance();
System.out.println(obj);

java.lang.reflect.Field類

方法名 作用
getModifiers 以int形式返回修飾符,默認修飾符是0,public是1,private是2,protected是4,static是8,final是16
getType 以Class形式返回類型
getName 返回屬性名稱
// 獲取類屬性
Field field = cls.getField("username");
field.set(obj , "admin");
System.out.println(field.get(obj));
// 獲取所有類屬性,private的成員變量沒有權限訪問
Field[] fields = cls.getFields();
for (Field field1 : fields) {
    System.out.println(field1.get(obj));
}
// 獲取所有類屬性包括private成員變量
Field[] allFields = cls.getDeclaredFields();
for (Field afield : allFields) {
    // 開放權限,私有的成員變量也能打印出來
    afield.setAccessible(true);
    System.out.println(afield.get(obj));
}

java.lang.reflect.Method類

方法名 作用
getModifiers 以int形式返回修飾符,默認修飾符是0,public是1,private是2,protected是4,static是8,final是16
getName 返回方法名
getReturnType 以class形式返回類型
getParmeterTypes 以Class[] 返回參數類型數組
// 獲取class方法,同樣默認情況不能獲取private的方法
Method method = cls.getMethod("hello");
System.out.println(method.invoke(obj));

java.lang.reflect.Constructor類

方法名 作用
getModifiers 以int形式返回修飾符,默認修飾符是0,public是1,private是2,protected是4,static是8,final是16
getName 返回方法名
getParmeterTypes 以Class[] 返回參數類型數組
Class<?> cls = Class.forName("com.example.core.example.reflection.domain.User");
Constructor<?> con = cls.getDeclaredConstructor();
con.setAccessible(true);
Object obj = con.newInstance();
System.out.println(obj);

9、反射開放權限操作

在我們使用Java反射獲取class的private成員變量或者方法時,這種情況是不允許獲取的,不過可以通過開放權限的方式來處理,通過設置setAccessible(true);即可

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * <pre>
 *      開放java反射權限,允許調用類的private屬性或者方法
 * </pre>
 * <p>
 * <pre>
 * @author mazq
 * 修改記錄
 *    修改後版本:     修改人:  修改日期: 2021/08/09 19:10  修改內容:
 * </pre>
 */
public class AccessibleReflectionExample {

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

        test();
    }

    public static void test() throws Exception{
        // 創建class實例對象
        Class<?> cls = Class.forName("com.example.core.example.reflection.domain.User");
        Constructor<?> con = cls.getDeclaredConstructor();
        con.setAccessible(true);
        Object obj = con.newInstance();

        // 獲取私有成員變量
        Field pwd = cls.getDeclaredField("password");
        // 開放私有變量的訪問權限
        pwd.setAccessible(true);
        pwd.set(obj , "123456");

        // 私有方法調用
        Method method = cls.getDeclaredMethod("priToString");
        method.setAccessible(true);
        System.out.println(method.invoke(obj));
    }
}

總結

本篇文章就到這裡瞭,希望能給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: