一文搞懂Java中的反射機制

一. 反射的概念

Java的反射機制是在運行狀態中,對於任何一個類,都可以知道這個類的所有屬性和方法,對於任何一個對象,都可以調用它所有的方法和屬性,修改部分類型信息,這種動態獲取信息以及動態調用對象方法的功能稱為Java的反射機制

二. 為什麼需要反射

在日常的第三方開發中,經常遇到某個類的方法或屬性是私有的,這時候就可以利用反射機制來獲取所需要的私有方法或屬性

我們在進行Java程序開發時,為瞭開發效率,一般會選擇IDE開發環境,IDE開發環境有一個強大的功能就是自動提示功能,IDE是如何知道對象中有哪些屬性和方法呢?

反射最重要的用途就是開發各種通用框架,比如在spring中,我們將所有的類Bean交給spring容器管理,無論是XML配置Bean還是註解配置,當我們從容器中獲取Bean來依賴註入時,容器會讀取配置,而配置中給的就是類的信息,spring根據這些信息,需要創建那些Bean,spring就動態的創建這些類

三. 反射的基石

反射的基石是字節碼文件對象

Java的源文件是不能直接進行運行的,需要先進行編譯為.class的字節碼文件,然後使用雙親委派模型被類加載器加載到虛擬機中形成字節碼文件對象,才可以在JVM中運行

何時才能觸發類的加載呢?隻要需要用類就會觸發類的加載,比如:

  1. new一個對象的時候
  2. 訪問一個靜態成員的時候
  3. 訪問一個靜態方法的時候
  4. 創建一個子類對象的時候
  5. java命令執行一個字節碼文件的時候
  6. 通過反射機制創建一個字節碼文件對象的時候

在Java中,一切皆對象,當字節碼文件加載到JVM中,會形成一個Class類對象,即該類在jvm中變成瞭一個對象

字節碼文件對象包含瞭三部分內容:

構造方法—Constructor對象

成員方法—Method對象

成員變量—Filed對象

四. 反射的實現

反射的第一步就是先獲取Class類對象,也就是字節碼文件對象,然後通過Class對象的核心方法達到反射的目的

1. 獲取字節碼文件對象 

獲取Class對象有三種方式:

  • 使用Class.forName("類的全路徑名"),可能會拋出ClassNotFoundException異常
  • 使用類名.class,需要在編譯期間就明確要操作的類
  • 使用對象.getClass()方法,需要先將對象創建出類

先創建一個Student類,將它的屬性,方法都設置為私有的

public class Student {
    private String name;
    private int age;
 
     public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    private String getName() {
        return name;
    }
 
    private void setName(String name) {
        this.name = name;
    }
 
    private int getAge() {
        return age;
    }
 
    private void setAge(int age) {
        this.age = age;
    }
 
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

這時候,Student類的全路徑名為:反射枚舉lambda.Student 

下面是獲取字節碼對象三種方式的代碼展示:

public class TestReflect {
    public static void main(String[] args) {
        //獲取字節碼文件對象
        //1.使用Class.forName("類的全路徑")
        try {
            Class<?> stuClass1 = Class.forName("反射枚舉lambda.Student");
            System.out.println(stuClass1);
            //2.使用類.class
            Class<?> stuClass2 = Student.class;
            System.out.println(stuClass2);
            System.out.println(stuClass1==stuClass2); //true,字節碼文件隻有一份,故是同一個對象
            //3.使用對象.getClass()
            //該方法需要先創建對象,故先將Student類的構造方法改為公有的再進行下述操作
            Student student = new Student("張三",26);
            Class<?> stuClass3 = student.getClass();
            System.out.println(stuClass3);
            System.out.println(stuClass2==stuClass3); //true,字節碼文件隻有一份,故是同一個對象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

打印結果:字節碼文件隻有一份,所以不同方式獲得的是同一個對象

2. 反射的使用 

2.1 反射構造方法創建實例

與反射相關的包都在import java.lang.reflect包下面

方法 說明
Constructor[] getConstructors() 獲取類中所有公有的構造器對象
Constructor<T> getConstructors(Class…<T> paramTypes) 獲取參數匹配的共有的構造器對象
Constructor[] getDeclaredConstructors() 獲取類中所有的構造器對象,包括私有的
Constructor<T> getDeclaredConstructors(Class…<T> paramTypes) 獲取類中參數匹配的構造器對象,包括私有的

具體步驟:

  • 獲取字節碼文件對象
  • 使用字節碼對象獲取構造方法
  • 設置構造方法權限
  • 使用構造方法創建實例對象 

代碼示例:

    public static void main(String[] args) {
        try {
            //1.獲取字節碼對象
            Class<?> stuClass = Class.forName("反射枚舉lambda.Student");
            //2.獲取構造方法
            Constructor<?> stuConstructor = stuClass.getDeclaredConstructor(String.class,int.class); //參數也是class類型
            //3.修改方法的訪問權限
            stuConstructor.setAccessible(true);
            //4.調用該方法
            Object object = stuConstructor.newInstance("李四",23); //newInstance()創建類的實例,為Object類型 
            Student s = (Student) object;
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

打印結果:

  

2.2 反射屬性

方法 說明
getFields() 獲取所有公有的屬性對象
getField(String name) 獲取某個公有的屬性對象
getDeclaredFields() 獲取所有的屬性對象,包括私有屬性
getDeclaredField(String name) 獲取某個屬性對象,包括私有屬性

具體步驟:

  • 獲取字節碼對象
  • 使用字節碼對象獲取屬性
  • 設置屬性權限
  • 調用方法設置屬性值

代碼示例:

//反射屬性
Field sutAge = stuClass.getDeclaredField("age"); //參數為屬性
sutAge.setAccessible(true);
sutAge.setInt(s,18); //設置屬性值為int,第一個參數為哪個對象,第二個參數為設置值
System.out.println(s);

打印結果:將對象s的age設置為18

2.3 反射方法

方法 說明
getMethods() 獲取該類所有的公有的方法
getMethod(String name,Class…<?> parameterTypes) 獲取該類某個公有的方法
getDeclaredMethods() 獲取該類所有方法,包括私有
getDeclaredMethod(String name,Class…<?> parameterTypes) 獲取該類某個方法,包括私有

具體步驟:

  • 獲取字節碼對象 
  • 使用字節碼對象獲取方法
  • 設置方法權限
  • 使用方法.invoke調用,第一個參數為哪個對象,後面參數為方法參數的具體值

代碼示例:

//反射方法
Method setNameMethod = stuClass.getDeclaredMethod("setName", String.class); //第一個參數為方法名,後面參數為方法參數
setNameMethod.setAccessible(true);
setNameMethod.invoke(s,"王五");
System.out.println(s);

打印結果:將對象s的姓名改為王五

反射的優缺點 

優點:

對於任意一個類,可以獲取該類的所有屬性和方法,對於一個對象,能調用它任意一個方法

增加程序的靈活性和擴展性,降低耦合性,提高自適應能力

反射已經應用在很多框架中,如:Spring,Struts,Hibernate 

缺點: 

破壞瞭類的封裝性

使用反射導致程序效率低

反射代碼比較復雜,因而會帶來維護問題 

以上就是一文搞懂Java中的反射機制的詳細內容,更多關於Java反射機制的資料請關註WalkonNet其它相關文章!

推薦閱讀: