關於 Java 反射,你該瞭解這些

爲什麼需要

正如我們知道的那樣,Java 程序中的許多實例化的對象在代碼被編寫以及代碼運行時都會出現兩種類型:編譯時類型(我們在 IDE 中敲出來的相關代碼)和運行時類型(執行敲的代碼)。比如:Person person = new Student() ,這段代碼生成了一個 person 變量,編譯時的類型是 Person,運行時類型爲 Student。這個時候,如果代碼中運行中需要接收一個外部傳入的對象,該對象編譯時的類型爲 Object,而程序又需要該對象運行時類型的方法。

對應的解決方案:

  1. 非常極端的情況:開發人員知道某個變量在編譯時以及運行時的具體信息,先使用 instanceof 運算符來判斷,然後再將其強制轉換爲運行時的類型的變量。
  2. 正常情況下,編譯時無法預知該對象以及類可能屬於哪些類,只能利用程序在運行時的信息來發現對象和類的信息,這個時候就用到了反射。

什麼是反射

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.——Core Java Reflection

簡而言之,就是通過反射,我們可以在 運行時 獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而 Java 反射機制可以動態地創建對象並調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。

如何實現

主要通過以下 API:

  1. java.lang.Class:代表一個類
  2. java.lang.reflect.Method:代表類的方法
  3. java.lang.reflect.Field: 代表類的成員變量
  4. java.lang.reflect.Constructor:代表類的構造器

功能及運用

實體類代碼如下:

package com.mindartisan.reflection;
public class Person {

    private String name;
    public int age;

    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;
    }

    public Person(String name, int age) {

        this.name = name;
        this.age = age;
    }

    private Person(String name) {
        this.name = name;
    }

    public Person() {
        System.out.println("Person()");
    }

    public void show(){
        System.out.println("你好,我是"+name);
    }

    private String showNation(String nation){
        System.out.println("我來自:" + nation);
        return nation;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

獲得 Class 對象

方式一:調用運行時類的屬性 .class

Class c1= Person.class;
System.out.println(c1);

方式二:通過運行時類的對象,調用 getClass()

Person p1 = new Person();
Class c2 = p1.getClass();
System.out.println(c2);

方式三:調用 Class 的靜態方法 forName(String classPath)

Class c3 = Class.forName("com.mindartisan.reflection");
ystem.out.println(c3);

方式四:使用類的加載器 ClassLoader

ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.mindartisan.reflection.Person");
System.out.println(c4);

判斷是否爲某個類的實例

一般情況下,我們可以使用 instanceof 來判斷是否爲某個類的實例,我們也可以藉助反射中的 Class 對象的 isInstance() 方法來判斷是否爲某個類的實例。

// public native boolean isInstance(Object obj);
Person person = new Person();
Class c5 = Person.class;
System.out.println(c5.isInstance(person));

Java 反射 isInstance(Object obj)

創建實例

方式一:使用 Class 的 newInstance() 方法來創建 Class 對象對應類的實例

Class c6 = Person.class;
/*
*要想此方法正常的創建運行時類的對象,要求:
* 1.運行時類必須提供空參的構造器
* 2.空參的構造器的訪問權限得夠。通常,設置爲public。
* 
* 在 javabean 中要求提供一個 public 的空參構造器。原因:
* 1.便於通過反射,創建運行時類的對象
* 2.便於子類繼承此運行時類時,默認調用super()時,保證父類有此構造器
*/
Object o = c6.newInstance();

方式二:先通過 Class 對象獲取指定的 Constructor 對象,再調用 Constructor 對象的 newInstance() 方法來創建實例

Class c7= Person.class;
Constructor cons = c7.getConstructor(String.class,int.class);
Object obj = cons.newInstance("Jobs", 30);
Person p2 = (Person) obj;
System.out.println(p2.toString());

獲取類的方法

  1. public Method[] getDeclaredMethods() 方法返回類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法;
  2. public Method[] getMethods() 方法返回某個類的所有公用(public)方法,包括其繼承類的公用方法;
  3. public Method getMethod(String name, Class<?>... parameterTypes) 方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應 Class 的對象。

獲取構造器信息

使用 Class 類的 getConstructor() 方法得到 Constructor 類的一個實例,而 Constructor 類有一個newInstance() 方法可以創建一個對象實例。若想獲取私有的構造器,使用 Class 類的 getDeclaredConstructor() 方法,如下:

Class c8= Person.class;
Constructor con1 = c8.getDeclaredConstructor(String.class);
con1.setAccessible(true);
Person p3 = (Person) con1.newInstance("Jobs");
System.out.println(p3);

獲取類的成員變量信息

類似「獲取類的方法」,此處不再贅述。

調用方法

當我們從類中獲取了一個方法後,我們就可以用 Method 類提供的 invoke() 方法來調用此方法。

Class c9 = Person.class;
Object obj1 = cons.newInstance("Jobs", 31);
Person p4 = (Person) obj1;
Method show1 = c9.getDeclaredMethod("show");
show.invoke(p4);

動態代理

請期待後續相關博客

加餐時刻

哪些類型有 Class 對象

  1. class:外部類、成員(成員內部類,靜態內部類),局部內部類,匿名內部類;
  2. interface:接口
  3. []:數組
  4. enum:枚舉
  5. annotation:註解
  6. 基本數據類型
  7. void

動態語言與靜態語言

動態語言: 在運行時代碼可以根據某些條件改變自身結構。
靜態語言: 運行時結構不可變的語言就是靜態語言。

注:Java 不是動態語言,但是它可以使用反射機制、字節碼操作獲得類似動態語言的特性。

推薦閱讀

深入解析 Java 反射

Java基礎之—反射

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