淺談Java的反射原理

糖拌蕃茄發表於2021-03-13

Java的編譯過程

談及反射,不得不先了解一下,java的整個編譯過程,整體的java編譯過程可以參考 之前的一篇 一個java檔案被執行的歷程 

這裡我們只針對 物件這一層級來討論,一個java檔案,我們經過編譯,會得出 一個 位元組碼檔案(.class),這時候,進入解釋階段,編譯器會將這個.class載入進記憶體中,此時,它首先會生成一個 Class物件。

Class物件與物件形成的過程

 Class物件

在java世界裡,一切皆物件。從某種意義上來說,java有兩種物件:例項物件和Class物件。對於每個類而言,JRE 都為其保留一個不變的 Class 型別的物件。

Class物件會有以下幾個特點:
  1. Class 物件只能由系統建立物件
  2. 一個類在 JVM 中只會有一個Class例項
  3. –每個類的例項都會記得自己是由哪個 Class 例項所生成

class物件包含的資訊有:

  • 資料成員名
  • 內部方法
  • 構造方法
  • 整合自哪個介面、類

 物件的形成過程

我們的例項物件是通過Class物件來建立的。

每一個類都有一個Class物件,每當編譯一個新類就產生一個Class物件,基本型別 (boolean, byte, char, short, int, long, float, and double)有Class物件,陣列有Class物件,就連關鍵字void也有Class物件(void.class)。Class物件對應著java.lang.Class類,如果說類是物件抽象和集合的話,那麼Class類就是對類的抽象和集合。

Class類沒有公共的構造方法,Class物件是在類載入的時候由Java虛擬機器以及通過呼叫類載入器中的 defineClass 方法自動構造的,因此不能顯式地宣告一個Class物件。

在類載入階段,類載入器首先檢查這個類的Class物件是否已經被載入。如果尚未載入,預設的類載入器就會根據類的全限定名查詢.class檔案。在這個類的位元組碼被載入時,它們會接受驗證,以確保其沒有被破壞,並且不包含不良java程式碼。一旦某個類的Class物件被載入記憶體,我們就可以它來建立這個類的所有物件。

反射的本質及應用

  上面的基礎瞭解完畢,我們進入今天的主體,何為反射。

所謂反射,官方的定義是: 指計算機程式在執行時(runtime) 可以訪問、檢測和修改它本身狀態或行為的一種能力。通俗說,反射就是程式在執行的時候能夠“觀察”並且修改自己的行為,是程式對自身的反思、自檢、修改。

  理解反射,首先得知道它的對立面,“正射”

“正射”

前面說到了Class物件,每個類的執行時的型別資訊就是用Class物件表示的。系統會自動建立唯一一個Class物件,它包含了與類有關的資訊。此時的java檔案(一個類)處於一箇中間狀態,並不是我們使用的物件,只有當我們使用  “ new  Object()”時,才會在JVM堆中根據這個Class物件來產生真正供我們使用的例項物件。其實也就是上面部分的物件的形成過程。

正射的使用意義是,我事先定義了一個物件的某些東西,然後當我需要的時候,我會通知記憶體去建立這個物件,然後我事先知道這個物件有什麼,所以我會精準的呼叫它的某個方法,某個成員變數。

用一個我們習以為常的demo來舉一下例:

class Human {
    String name;
    int age;
    String nation;
    Human(String name,int age,String nation) {
        this.name=name;
        this.age=age;
        this.nation=nation;
    }
    void changeName(String name){
        this.name=name;
    }
}
public class Main {
    public static void main(String[] args){
        Human human=new Human("張三",22,"中國");
        human.changeName("李四");
    }
}

在上面Main類的main方法中,我之所以可以寫human.changeName(“張三”) 是因為Human類也是我自己寫的,我清楚的知道,human作為Human的例項物件,可以呼叫changeName方法,如果我手抖寫了呼叫changeNamee方法,我也會立即改回來,因為我知道Human裡沒有這個方法。假如我不知道Human裡有沒有一個改名字的方法,即Human類對我來說是不透明的第三方類,我嘗試性的在程式中呼叫了一個newName方法,儲存、編譯。這時候編譯器會通知我,這樣寫程式是不對的,Human裡沒有一個叫newName的方法,編譯失敗。

反射

假如此時我只想使用物件某一個方法,某一個成員變數,而不想用其他的部分,這時候如果用“正射”(走正常的物件建立過程,new一下),就會被迫建立一個完整的該物件(有一說一,有點浪費),此時我可以根據類的全路徑+名稱,去記憶體中拿出這個類的Class物件,根據這個Class物件,靈活的去獲取這個類片面的資訊。這種在執行時訪問、修改物件的狀態和行為,可以給程式帶來極大的靈活性。這便是反射

反射可提供的功能

  1. 在執行時判斷任意一個物件所屬的類。

  2. 在執行時構造任意一個類的物件。

  3. 在執行時判斷任意一個類所具有的成員變數和方法。

  4. 在執行時呼叫任意一個物件的方法。

  5. 生成動態代理。

詳細的API使用,可以查閱java的官方文件。

反射的使用

  使用Java註解配合反射可以開發出各種工具、框架。例如,Spring中的註解、工廠模式中建立物件的方式等

這裡實現一個用自定義註解 @AutoField 實現為屬性賦值。

定義註解@AutoField 

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoField {
    String value() default "";
}

編寫解析類。BeanFactory中的createBean方法通過反射拿到註解 @AutoField的值並賦給物件。

public class BeanFactory {
    public static <T> T createBean(Class<T> clazz) {
        T o = null;
        try {
            o = clazz.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(AutoField.class)) {
                AutoField AutoField = declaredField.getAnnotation(AutoField.class);
                if (!declaredField.isAccessible())
                    declaredField.setAccessible(true);
                try {
                    if (declaredField.getType().getSimpleName().equals("String"))
                        declaredField.set(o, AutoField.value());
                    else if (declaredField.getType().getSimpleName().equals("byte"))
                        declaredField.set(o, Byte.parseByte(AutoField.value()));
                    else if (declaredField.getType().getSimpleName().equals("short"))
                        declaredField.set(o, Short.parseShort(AutoField.value()));
                    else if (declaredField.getType().getSimpleName().equals("int"))
                        declaredField.set(o, Integer.parseInt(AutoField.value()));
                    else if (declaredField.getType().getSimpleName().equals("long"))
                        declaredField.set(o, Long.parseLong(AutoField.value()));
                    else if (declaredField.getType().getSimpleName().equals("float"))
                        declaredField.set(o, Float.parseFloat(AutoField.value()));
                    else if (declaredField.getType().getSimpleName().equals("double"))
                        declaredField.set(o, Double.parseDouble(AutoField.value()));
                    else if (declaredField.getType().getSimpleName().equals("long"))
                        declaredField.set(o, Long.parseLong(AutoField.value()));
                    else if (declaredField.getType().getSimpleName().equals("boolean"))
                        declaredField.set(o, Boolean.parseBoolean(AutoField.value()));
                    else
                        throw new RuntimeException(declaredField.getName() + " of " + clazz.getName() + " is not a value field");
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }

            }
        }
        return o;
    }
}

  定義實體類

public class Teacher {

  @AutoField("12223")
  private int id;

  @AutoField("Zhang")
  private String name;

  @AutoField("20")
  private int age;

  @AutoField("false")
  private boolean isProfessor;

  @AutoField("G")
  private String sex;

  @AutoField("CQU")
  private String school;

  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  public boolean isProfessor() {
    return isProfessor;
  }

  public String getSex() {
    return sex;
  }

  public String getSchool() {
    return school;
  }

  public void setId(int id) {
    this.id = id;
  }

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

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

  public void setProfessor(boolean professor) {
    isProfessor = professor;
  }

  public void setSex(String sex) {
    this.sex = sex;
  }

  public void setSchool(String school) {
    this.school = school;
  }

  @Override
  public String toString() {
    return "Teacher{" +
      "id=" + id +
      ", name='" + name + '\'' +
      ", age=" + age +
      ", isProfessor=" + isProfessor +
      ", sex=" + sex +
      ", school='" + school + '\'' +
    '}';
  }
}

 

測試

public class Main {
    public static void main(String[] args) {
        Teacher teacher = BeanFactory.createBean(Teacher.class);
        System.out.println(teacher);
    }

 

 

 

  

相關文章