淺談Java的反射原理
Java的編譯過程
談及反射,不得不先了解一下,java的整個編譯過程,整體的java編譯過程可以參考 之前的一篇 一個java文件被執行的歷程
這裡我們只針對 對象這一層級來討論,一個java文件,我們經過編譯,會得出 一個 位元組碼文件(.class),這時候,進入解釋階段,編譯器會將這個.class載入進記憶體中,此時,它首先會生成一個 Class對象。
Class對象與對象形成的過程
Class對象
在java世界裡,一切皆對象。從某種意義上來說,java有兩種對象:實例對象和Class對象。對於每個類而言,JRE 都為其保留一個不變的 Class 類型的對象。
- Class 對象只能由系統建立對象
- 一個類在 JVM 中只會有一個Class實例
- –每個類的實例都會記得自己是由哪個 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對象,靈活的去獲取這個類片面的資訊。這種在運行時訪問、修改對象的狀態和行為,可以給程式帶來極大的靈活性。這便是反射
反射可提供的功能
-
在運行時判斷任意一個對象所屬的類。
-
在運行時構造任意一個類的對象。
-
在運行時判斷任意一個類所具有的成員變數和方法。
-
在運行時調用任意一個對象的方法。
-
生成動態代理。
詳細的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); }