高級 Java 思考筆記之反射的基本原理之一
原來我一直將java當(dāng)做不用delete的c++來用,但是最近在工作上遇到一些問題,發(fā)現(xiàn)這樣的做法不管用,因為工作上遇到的java代碼采用了很多框架,例如spring boot之類。當(dāng)我想深入了解這些框架的設(shè)計原理時發(fā)現(xiàn),如果不了解java的高級語法特性,這些框架的設(shè)計思維或運行方式根本就無法理解。
后來稍微調(diào)查一下發(fā)現(xiàn),自從java8之后,java的語法發(fā)生了巨大的變化,代碼的設(shè)計模式也不再像以前“不用delete的c++”,參照原來c++的思維去解讀java代碼已經(jīng)行不通了,于是重新學(xué)習(xí),順便在學(xué)習(xí)和思考中做一些總結(jié)輸出。
java新增語法特征中,有較為抽象的一部分叫反射,其實說白了就是用程序來控制程序。java將一切概念都包含在類中,于是對應(yīng)每一個具體類,語言體系還為特定的具體類生成了描述其特性的抽象類,例如我們定義了如下一個類:
- static public class HelloWorld {
- public void sayHelloWorld1(String s) {
- System.out.println("say hello world 1!: " + s);
- }
- public void sayHelloWorld2(int i) {
- System.out.println("say hello world 2: " + i);
- }
- protected void sayHelloWorld3() {
- System.out.println("say hello world3");
- }
- private void sayHelloworld4() {
- System.out.println("say hello world4");
- }
- public String field1 = "hello";
- protected int field2 = 1;
- private int field3 = 2;
- }
這個類里面有公有,保護(hù),私有等幾個方法,虛擬機在為這個類生成字節(jié)碼時,還構(gòu)建了另一個類,用于描述這個類的類,可以稱為它的源類,后者用來描述前者在編程語言上的特性,例如它包含了哪些方法,這些方法對應(yīng)的參數(shù),返回值,公有私有等,如果把一個類比作人,那么反射其實就是x光掃描,它把你內(nèi)外的具體細(xì)節(jié)都暴露出來,我們看看怎么用反射來解讀上面定義的類:
- public static void manipulateHelloWorldClass(Object obj) {
- Class cls = obj.getClass();
- //打印出類對象對應(yīng)的類名
- System.out.println("object class name: " + cls.getName());
- //返回實例對應(yīng)的類所聲明的所有函數(shù)
- Method[] methods = cls.getDeclaredMethods();
- for (Method method : methods) {
- System.out.println("delcared method name: " + method.getName());
- }
- }
- public static void main(String[] args) {
- HelloWorld helloWorld = new HelloWorld();
- manipulateHelloWorldClass(helloWorld);
- }
上面代碼運行后輸出結(jié)果如下:
可以看到,代碼打印出helloword實例對象在編程語言上的信息,例如上面代碼就打印出它對應(yīng)類的名字,都有哪些方法等等,在Java語言中Class類也叫原類,它用來解析所有實例對應(yīng)類在編程語法上的信息,每個實例對應(yīng)的類都能得到一個Class類對應(yīng)的實例,就像代碼中那樣,這個實例能夠查詢對應(yīng)類有哪些方法,定義了哪些字段.
代碼中調(diào)用了getDeclaredMethods來獲得實例所對應(yīng)類在定義時聲明的所有方法,Class類還有一個方法叫g(shù)etMethods,它返回實例對應(yīng)類自己在定義時所聲明的公有方法,以及繼承過來的所有公有方法。
反射機制一個很重要的作用就是能夠查詢給定實例是否有特定接口,然后調(diào)用相關(guān)接口,代碼如下:
- public static void callMethod(Object obj) {
- Class cls = obj.getClass();
- try {
- Method method = cls.getMethod("sayHelloWorld1", new Class[] {String.class});
- method.invoke(obj, new Object[]{"hello world"});
- method = cls.getMethod("sayHelloWorld2", new Class[] {int.class});
- method.invoke(obj, new Object[]{new Integer(123)});
- } catch(NoSuchMethodException e) {
- e.printStackTrace();;
- } catch (IllegalAccessException e) {
- throw new IllegalArgumentException("Insufficient access permissions to call"
- + "setColor(:Color) in class " + cls.getName());
- } catch (InvocationTargetException ex) {
- throw new RuntimeException(ex);
- }
- }
- public static void main(String[] args) {
- HelloWorld helloWorld = new HelloWorld();
- // manipulateHelloWorldClass(helloWorld);
- callMethod(helloWorld);
- }
上面代碼執(zhí)行后結(jié)果如下所示:
我們可以體會到,c++是不會有這種特性的,java由于具備了這種反射機制,使得它能夠用來開發(fā)很多框架,在java世界里形形色色的框架特別多,這跟它在語法上支持反射不無關(guān)系,類似spring boot這些java程序員絕對必須要掌握的框架,它的設(shè)計就大量使用了反射機制。
使用反射功能還可以很好的實現(xiàn)類實例的序列化,當(dāng)我們想要將一個類實例的信息從內(nèi)存存儲到硬盤時,我們就需要將類實例當(dāng)前內(nèi)部各個字段的信息存儲到文件里,以后需要的時候再從文件中讀出,然后利用讀到的數(shù)據(jù)重新把類的實例new出來,因此實現(xiàn)序列化第一步就是要獲得類實例所有字段的數(shù)據(jù),相應(yīng)代碼如下:
- import java.lang.reflect.*;
- import java.util.*;
- public class Serializer {
- public static Field[] getInstanceVariable(Object obj) {
- Class cls = obj.getClass();
- List accumFields = new LinkedList();
- while (cls != null) {
- //獲得實例對應(yīng)類所聲明的全部字段
- Field[] fields = cls.getDeclaredFields();
- for (Field field : fields) {
- accumFields.add(field); //將字段對應(yīng)的元類對象存儲起來
- }
- cls = cls.getSuperclass(); //獲得父類對象
- }
- }
- }
代碼中要注意到,實例化一個類實例時,還需要考慮這個類的繼承關(guān)系,如果它有父類的話,我們還需要取得其父類的字段對應(yīng)的信息,所以代碼中使用getSupperclass方法獲得實例對應(yīng)類的父類的源類對象。
前面代碼中我們看到,F(xiàn)ield類對應(yīng)的getModifier能返回字段的修飾屬性,也就是字段是public, private, protected , static, native等等,它返回的是一個2的指數(shù)冪數(shù)值,實際上它對應(yīng)一個16比特位數(shù)值,當(dāng)字段屬于哪種情況,就在相應(yīng)的比特位上設(shè)置為1,因為字段的屬性總共有16種,因此getModifier返回一個2字節(jié)的整數(shù)。
得到這個數(shù)值后,我們再調(diào)用一系列方法獲得其屬性,例如isPublic返回字段是否是public類型,isPrivate返回字段是否為private類型等。由于我們在序列化一個實例時,不用關(guān)注那些靜態(tài)變量,因為靜態(tài)變量的值是寫死的,因此需要對上面的代碼進(jìn)行修改,忽略掉那些被static修飾的字段:
- import java.lang.reflect.*;
- import java.util.*;
- public class Serializer {
- public static Field[] getInstanceVariable(Object obj) {
- Class cls = obj.getClass();
- List accumFields = new LinkedList();
- while (cls != null) {
- //獲得實例對應(yīng)類所聲明的全部字段
- Field[] fields = cls.getDeclaredFields();
- for (Field field : fields) {
- //確保字段不是static類型
- if(!Modifier.isStatic(field.getModifiers())) {
- accumFields.add(field);
- }
- }
- cls = cls.getSuperclass(); //獲得父類對象
- }
- Field[] retvalue = new Field[accumFields.size()];
- return (Field[])accumFields.toArray(retvalue);
- }
- }
這里還有很多問題需要考慮,例如序列化實例時,我們需要獲取類實例里面字段的值,但是如果字段屬性是private或者protected時,我們就不能直接從類實例中讀取字段內(nèi)容,同時如果字段對應(yīng)的是數(shù)組類型,那么我們還得采取特定的處理方法,為了防止文章長度過長令人看不下去,我們把這些內(nèi)容總結(jié)再下一篇。