Java 中的 反射機制

概念明確

什麼是類的對象?

  類的對象就是:基於某個類 new 出來的對象,也稱為實例對象。這個很容易理解,就不過多闡述了。

什麼是類對象?

  類對象就是:類載入的產物,封裝了一個類的所有資訊(類名、父類、介面、屬性、方法、構造方法)。

  包含類資訊的.class文件被JVM載入到記憶體後,一個個的類就變成了一個個的 java.long.Class 對象,每個類有且只有一個java.long.Class對象,這些對象包含了類的全部資訊,這些對象存儲在方法區中。

  每一個對象實例和創建它的類對象之間都有一條類型引用,代表著此實例是這個類型的。

 


反射機制

什麼是反射機制?

  在 Java 中要使用一個類首先要將該類載入到記憶體中,系統會為該類生成一個java.lang.Class的實例。這個 Class 對象的作用很大,通過它系統可以訪問到JVM中該類的資訊,同時 Class 對象也是實現 Java 反射機制的核心要素。

反射(Reflection)是指:

  程式可以訪問、檢測和修改它本身狀態或行為的一種能力,並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。

  上面的解釋並不太通俗易懂,我們看看下面的闡述:

  在高級語言中,允許改變程式結構或變數類型的語言稱為動態語言,例如Perl、Python、Ruby等就是動態語言,而像C、C++、Java這類語言在程式編譯時就確定了程式的結構和變數的類型,因此不是動態語言。儘管如此,Java還是為開發者提供了一個非常有用的與動態相關的機制-反射(Reflection)。

  運用反射機制可以在運行時載入和使用編譯期間未知的類型。也就是說,Java程式可以載入在運行時才得知類名的class,並生成其對象實體,或訪問其屬性,或喚起其成員方法。通俗點講,所謂Java的反射機制,就是在Java程式運行時動態地載入並使用在編譯期並不知道的類。

 


反射機制的作用

  ① 獲取一個類的資訊。

  ② 通過類型的名稱動態生成並操作對象。

 


獲取一個類的Class對象

  通過一個類的Class對象可以獲取該類的資訊,包括類的構造函數、屬性、方法等,那麼如何來獲取一個類的Class對象呢?

在Java中獲取一個類的Class對象的方法有3種:

  ➷ Class.forName(className)。『推薦使用』

  ➷ 調用某個類的class屬性獲取該類對應的Class對象。

  ➷ 調用對象的getClass()方法獲取該對象所屬類對應的Class對象。

程式碼演示:

// Person 類
public class Person implements Serializable, Cloneable{
    private String name;
    private int age;
    
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public Person() {
        super();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
    
    public void eat() {
        System.out.println(name + "正在吃東西。。。");
    }
    
    public void eat(String food) {
        System.out.println(name + "正在吃" + food + "。。。");
    }
    
    private void privateMethod() {
        System.out.println("這是一個私有方法");
    }
    
    public static void staticMethod() {
        System.out.println("這是一個靜態方法");
    }
    
}
public class Test {

    public static void main(String[] args) throws Exception {
        // 1.創建類的對象
        Person zhangsan = new Person("張三", 18);
        System.out.println(zhangsan.toString());
        
        // 2.獲取類對象
        
        // 方式一:通過類的對象,獲取類對象
        Person s = new Person();
        Class<?> c = s.getClass();
        System.out.println(c.toString());
        
        // 方式二:通過類名獲取類對象
        Class<?> c1 = Person.class;
        System.out.println(c1.toString());
        
        // 方式三:通過靜態方法獲取類對象 [推薦使用]
        Class<?> c2 = Class.forName("com.ruoli.reflect.Person");
        System.out.println(c2.toString());
    }
}

運行結果:

  一般情況下生成一個類的對象是不需要使用反射的。但是有些特殊的情況必須用到反射才能實現類的實例化。比如要對一個類進行操作,但是這個類的類名需要從配置文件中讀取,事先並不知道該類的類名等資訊。這樣在編譯時就無法確定該類的類名,也就無法按照傳統的方法構建類的對象了。所以可以先讀取配置文件並拿到這個類的全類名,然後利用反射生成該類的對象,並進行相應的操作。一個很好的例子就是設計模式中的簡單工廠模式。

  綜上所述,所以大多數情況下都是使用Class.forName(className)這種方法來獲取類對象的,也推薦大家在日常編程中多實用這種方法。


獲取類的名字,包名,父類,介面

程式碼演示:

public class Test {

    public static void main(String[] args) throws Exception {
        // 1.獲取類對象
        Class<?> c2 = Class.forName("com.ruoli.reflect.Person");
        
        // 2.獲取類的名字
        System.out.println("類名:" + c2.getName());
        
        // 3.獲取類的包名
        System.out.println("包名:" + c2.getPackage().getName());
        
        // 4.獲取類的父類
        System.out.println("父類:" + c2.getSuperclass().getName());
        
        // 5.獲取類的介面
        Class<?>[] classes = c2.getInterfaces();
        System.out.println("介面:" + Arrays.toString(classes));
        
        // 6.獲取類的簡稱
        System.out.println("簡稱:" + c2.getSimpleName());
    }
}

運行結果:

 


獲取類的構造方法,並創建對象

程式碼演示:

public class Test {

    public static void main(String[] args) throws Exception {
        // 1.獲取類對象
        Class<?> c2 = Class.forName("com.ruoli.reflect.Person");
        
        // 2.獲取類的構造方法        
        // 2.1獲取全部的構造方法
//        Constructor<?>[] cons = c2.getConstructors();
//        for (Constructor<?> constructor : cons) {
//            System.out.println(constructor.toString());
//        }
        
        // 2.2獲取無參的構造方法,創建對象
//        Constructor<?> con = c2.getConstructor();
//        System.out.println(con.toString());
//
//        Person zhangsan = (Person)con.newInstance();
//        System.out.println(zhangsan.toString());
        
        // 簡單方法:實際上就是調用上面的無參構造
//        Person xiaoming = (Person)c2.newInstance();
//        System.out.println(xiaoming.toString());
        
        // 2.3獲取帶參的構造方法,創建對象
        Constructor<?> con = c2.getConstructor(String.class, int.class);
        System.out.println(con.toString());
        
        Person xiaoming = (Person)con.newInstance("小明", 19);
        System.out.println(xiaoming.toString());
    }
}

運行結果:

 


獲取類中的方法,並調用方法

程式碼演示:

public class Test {

    public static void main(String[] args) throws Exception {
        // 1.獲取類對象
        Class<?> c2 = Class.forName("com.ruoli.reflect.Person");
        
        // 2.獲取類的方法
        
        // 2.1 getMethods() 只能獲取公開的方法,包括從父類繼承的方法
//        Method[] methods = c2.getMethods();
//        for (Method method : methods) {
//            System.out.println(method.toString());
//        }
        
        // 2.2 getDeclaredMethods() 獲取類中的所有方法,包括私有、默認、保護的方法,不包含繼承的
//        Method[] methods = c2.getDeclaredMethods();
//        for (Method method : methods) {
//            System.out.println(method.toString());
//        }
        
        // 2.3獲取單個方法   
        
        // 獲取無參、無返回值的方法eat(): 
        Method eatMethod = c2.getMethod("eat");
        System.out.println(eatMethod.toString());
        Person zhangsan = (Person)c2.newInstance();
        eatMethod.invoke(zhangsan);
        
        // 獲取無參、有返回值的方法toString(): 
        Method toStringMethod = c2.getMethod("toString");
        System.out.println(toStringMethod.toString());
        Object result = toStringMethod.invoke(zhangsan);
        System.out.println(result);
        
        // 獲取帶參、無返回值的方法eat(String food): 
        Method eatMethod2 = c2.getMethod("eat", String.class);
        System.out.println(eatMethod2.toString());
        eatMethod2.invoke(zhangsan, "雞腿");

        // 獲取私有方法privateMethod()
        Method privateMethod = c2.getDeclaredMethod("privateMethod");
        System.out.println(privateMethod.toString());
        // 此時調用方法(私有方法),沒有訪問許可權
        // 設置訪問許可權無效
        privateMethod.setAccessible(true);
        privateMethod.invoke(zhangsan);
        
        // 獲取靜態方法staticMethod()
        Method staticMethod = c2.getMethod("staticMethod");
        System.out.println(staticMethod.toString());
        staticMethod.invoke(null);
    }
}

運行結果:

  這裡需要注意的是,當我們通過反射獲取一個類的私有方法時,我們是沒有許可權來使用的,因此需要設置訪問許可權,然後才能使用這個私有方法。

 


獲取類中的屬性

程式碼演示:

public class Test {

    public static void main(String[] args) throws Exception {
        // 1.獲取類對象
        Class<?> c2 = Class.forName("com.ruoli.reflect.Person");
        
        // 2.獲取屬性    公開的欄位,父類繼承的欄位
//        Field[] fields = c2.getFields();
//        System.out.println(fields.length);
        
        // 2.獲取屬性    獲取所有的屬性,但不包含繼承的
//        Field[] fields = c2.getDeclaredFields();
//        System.out.println(fields.length);
//        for (Field field : fields) {
//            System.out.println(field);
//        }
        
        // 2.獲取單個屬性
        Field nameField = c2.getDeclaredField("name");
        nameField.setAccessible(true);
        // 賦值
        Person zhangsan = (Person)c2.newInstance();
        nameField.set(zhangsan, "張三");
        // 獲取值
        System.out.println(nameField.get(zhangsan));
    }
}

運行結果:

 


反射機制總結

反射機制

  所謂Java的反射機制,就是在Java程式運行時動態地載入並使用在編譯期並不知道的類。

反射機制的作用

  ➷ 反射機制可以動態獲取一個類的資訊,包括該類的屬性和方法,這個功能可應用於對class文件進行反編譯。

  ➷ 反射機制也可以通過類型的名稱動態生成對象,並調用對象中的方法。

反射機制的優缺點

  ✔ 優點:可以在運行時獲取一個類的實例,大大提高了系統的靈活性和可擴展性。

  ✘ 缺點:性能較差,安全性不高,破壞了類的封裝性。