摘要:不管是什么樣的框架,其都涉及到反射。見名知其意,即類對象,其包含了類的所有信息,包括屬性方法構造器。為了生成這個類的對象,運行當前程序的將使用到類加載器。這種是等主流框架使用的。
導讀
源碼地址
在之后的幾篇文章,我會講解我自己的hibernate、spring、beanutils框架,但講解這些框架之前,我需要講解RTTI和反射。
工作將近一年了,我們公司項目所使用的框架是SSH,或者,其他公司使用的是SSM框架。不管是什么樣的框架,其都涉及到反射。那么,什么是反射?我們在生成對象時,事先并不知道生成哪種類型的對象,只有等到項目運行起來,框架根據我們的傳參,才生成我們想要的對象。
比如,我們從前端調用后端的接口,查詢出這個人的所有項目,我們只要傳遞這個人的id即可。當然,數據來源于數據庫,那么,問題來了,數據是怎么從持久態(tài)轉化成我們想要的順時態(tài)的?這里面,就涉及到了反射。但是,一提到反射,我們勢必就提到RTTI,即運行時類型信息(runtime Type Infomation)。
RTTIpo類
/** * Created By zby on 16:53 2019/3/16 */ @AllArgsConstructor @NoArgsConstructor public class Pet { private String name; private String food; public void setName(String name) { this.name = name; } public void setFood(String food) { this.food = food; } public String getName() { return name; } public String getFood() { return food; } } /** * Created By zby on 17:03 2019/3/16 */ public class Cat extends Pet{ @Override public void setFood(String food) { super.setFood(food); } } /** * Created By zby on 17:04 2019/3/16 */ public class Garfield extends Cat{ @Override public void setFood(String food) { super.setFood(food); } } /** * Created By zby on 17:01 2019/3/16 */ public class Dog extends Pet{ @Override public void setFood(String food) { super.setFood(food); } }
以上是用來說明的persistent object類,也就是,我們在進行pojo常用的javabean類。其有繼承關系,如下圖:
展示信息
如下代碼所示,方法eatWhatToday有兩個參數,這兩個參數一個是接口類,一個是父類,也就是說,我們并不知道打印出的是什么信息。只有根據接口的實現(xiàn)類來和父類的子類,來確認打印出的信息。這就是我們說的運行時類型信息,正因為有了RTTI,java才有了動態(tài)綁定的概念。
/** * Created By zby on 17:05 2019/3/16 */ public class FeedingPet { /** * Created By zby on 17:05 2019/3/16 * 某種動物今天吃的是什么 * * @param baseEnum 枚舉類型 這里表示的時間 * @param pet 寵物 */ public static void eatWhatToday(BaseEnum baseEnum, Pet pet) { System.out.println( pet.getName() + "今天" + baseEnum.getTitle() + "吃的" + pet.getFood()); } }
測試類
@Test public void testPet(){ Dog dog=new Dog(); dog.setName("寵物狗京巴"); dog.setFood(FoodTypeEnum.FOOD_TYPE_BONE.getTitle()); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MORNING,dog); Garfield garfield=new Garfield(); garfield.setName("寵物貓加菲貓"); garfield.setFood(FoodTypeEnum.FOOD_TYPE_CURRY.getTitle()); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,garfield); }
打印出的信息為:
那么,這和反射有什么關系呢?
反射獲取當前類信息正如上文提到的運行時類型信息,那么,類型信息在運行時是如何表示的?此時,我們就想到了Class這個特殊對象。見名知其意,即類對象,其包含了類的所有信息,包括屬性、方法、構造器。
我們都知道,類是程序的一部分,每個類都有一個Class對象。每當編寫并且執(zhí)行了一個新類,就會產生一個Class對象(更恰當地說,是被保存在一個同名的.class文件中)。為了生成這個類的對象,運行當前程序的jvm將使用到類加載器。jvm首先調用bootstrap類加載器,加載核心文件,jdk的核心文件,比如Object,System等類文件。然后調用plateform加載器,加載一些與文件相關的類,比如壓縮文件的類,圖片的類等等。最后,才用applicationClassLoader,加載用戶自定義的類。
加載當前類信息反射正是利用了Class來創(chuàng)建、修改對象,獲取和修改屬性的值等等。那么,反射是怎么創(chuàng)建當前類的呢?
第一種,可以使用當前上下文的類路徑來創(chuàng)建對象,如我們記載jdbc類驅動的時候,如以下代碼:
/** * Created By zby on 18:07 2019/3/16 * 通過上下文的類路徑來加載信息 */ public static Class byClassPath(String classPath) { if (StringUtils.isBlank(classPath)) { throw new RuntimeException("類路徑不能為空"); } classPath = classPath.replace(" ", ""); try { return Class.forName(classPath); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; }
第二種,通過類字面常量,這種做法非常簡單,而且更安全。因為,他在編譯時就會受到檢查,我們不需要將其置于try catch的代碼快中,而且,它根除了對forName的方法調用,所以,更高效。這種是spring、hibernate等主流框架使用的。
框架hibernate的內部使用類字面常量去創(chuàng)建對象后,底層通過jdbc獲取數據表的字段值,根據數據表的字段與當前類的屬性進行一一匹配,將字段值填充到當前對象中。匹配不成功,就會報出相應的錯誤。
類字面常量獲取對象信息,如代碼所示。下文,也是通過類字面常量創(chuàng)建對象。
/** * Created By zby on 18:16 2019/3/16 * 通過類字面常量加載當前類的信息 */ public static void byClassConstant() { System.out.println(Dog.class); }
第三種,是通過對象來創(chuàng)建當前類,這種會在框架內部使用。
/** * Created By zby on 18:17 2019/3/16 * 通過類對象加載當前類的信息 */ public static Class byCurrentObject(Object object) { return object.getClass(); }反射創(chuàng)建當前類對象
我們創(chuàng)建當前對象,一般有兩種方式,一種是通過clazz.newInstance();這種一般是無參構造器,并且創(chuàng)建對對象后,可以獲取其屬性,通過屬性賦值和方法賦值,如如代碼所示:
第一種,通過clazz.newInstance()創(chuàng)建對象
/** * Created By zby on 18:26 2019/3/16 * 普通的方式創(chuàng)建對象 */ public staticT byCommonGeneric(Class clazz, String name, BaseEnum baseEnum) { if (null == clazz) { return null; } try { T t = (T) clazz.newInstance(); //通過屬性賦值,getField獲取公有屬性,獲取私有屬性 Field field = clazz.getDeclaredField("name"); //跳過檢查,否則,我們沒辦法操作私有屬性 field.setAccessible(true); field.set(t, name); //通過方法賦值 Method method1 = clazz.getDeclaredMethod("setFood", String.class); method1.setAccessible(true); method1.invoke(t, baseEnum.getTitle()); return t; } catch (Exception e) { e.printStackTrace(); } return null; } 測試: @Test public void testCommonGeneric() { Dog dog= GenericCurrentObject.byCommonGeneric(Dog.class, "寵物狗哈士奇", FoodTypeEnum.FOOD_TYPE_BONE); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_NOON,dog); }
叔叔出結果為:
你會發(fā)現(xiàn)一個神奇的地方,就是名字沒有輸出來,但我們寫了名字呀,為什么沒有輸出來?因為,dog是繼承了父類Pet,當我們在創(chuàng)建子類對象時,首先,會加載父類未加載的構造器、靜態(tài)代碼塊、靜態(tài)屬性、靜態(tài)方法等等。但是,Dog在這里是以無參構造器加載的,當然,同時也通過無參構造器的實例化了父類。我們在給dog對象的name賦值時,并沒有給父類對象的name賦值,所以,dog的name是沒有值的。父類引用指向子類對象,就是這個意思。
如果我們把Dog類中的 @Override public void setFood(String food) {super.setFood(food); }的super.setFood(food); 方法去掉,屬性food也是沒有值的。如圖所示:
通過構造器創(chuàng)建對象
/** * Created By zby on 18:26 2019/3/16 * 普通的方式創(chuàng)建對象 */ public staticT byConstruct(Class clazz, String name, BaseEnum baseEnum) { if (null == clazz) { return null; } // 參數類型, Class paramType[] = {String.class, String.class}; try { // 一般情況下,構造器不止一個,我們根據構器的參數類型,來使用構造器創(chuàng)建對象 Constructor constructor = clazz.getConstructor(paramType); // 給構造器賦值,賦值個數和構造器的形參個數一樣,否則,會報錯 return (T) constructor.newInstance(name, baseEnum.getTitle()); } catch (Exception e) { e.printStackTrace(); } return null; } 測試: @Test public void testConstruct() { Dog dog= GenericCurrentObject.byConstruct(Dog.class, "寵物狗哈士奇", FoodTypeEnum.FOOD_TYPE_BONE); System.out.println("輸出寵物的名字:"+dog.getName()+" "); System.out.println("寵物吃的什么:"+dog.getFood()+" "); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,dog); }
測試結果:
這是通過構造器創(chuàng)建的對象。但是注意的是,形參類型和和參數值的位數一定要相等,否則,就會報出錯誤的。
總結為什么寫這篇文章,前面也說了,很多框架都用到了反射和RTTI。但是,我們的平常的工作,一般以業(yè)務為主。往往都是使用別人封裝好的框架,比如spring、hibernate、mybatis、beanutils等框架。所以,我們不大會關注反射,但是,你如果想要往更高的方向去攀登,還是要把基礎給打撈。否則,基礎不穩(wěn),爬得越高,摔得越重。
我會以后的篇章中,通過介紹我寫的spring、hibernate框架,來講解更好地講解反射。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73736.html
摘要:接口與類型信息關鍵字的一種重要目標就是允許程序員隔離構件,進而降低耦合性。如果你編寫接口,那么就可以實現(xiàn)這一目標,但是通過類型信息,這種耦合性還是會傳播出去接口并非是對解耦的一種無懈可擊的保障。 點擊進入我的博客 運行時類型信息使得你可以在運行時發(fā)現(xiàn)和使用類型信息,主要有兩種方式: 傳統(tǒng)的RTTI,它假定我們在編譯時已經知道了所有的類型; 反射機制,它允許我們在運行時發(fā)現(xiàn)和使用類的...
摘要:因而,我從中也知道了,很多公司沒有實現(xiàn)數據過濾。因為,那樣將會造成數據的冗余。因而,我們這時需要過濾數據對象,如代碼所示常用的把圖片轉成結構如上訴代碼的轉換,公司使用的是這個框架。而棧是存放數據的一種結構,其采用,即先進后出。 導讀 上一篇文章已經詳細介紹了框架與RTTI的關系,RTTI與反射之間的關系。尤其是對反射做了詳細說明,很多培訓機構也將其作為高級教程來講解。 其實,我工作年限...
摘要:同時也有一些兒高級的處理,比如批處理更新事務隔離和可滾動結果集等。連接對象表示通信上下文,即,與數據庫中的所有的通信是通過此唯一的連接對象。因為是針對類的關系而言,所以一個對象對應多個類的實例化。返回表示查詢返回表示其它操作。 JDBC是什么? JDBC是一個Java API,用中文可以通俗的解釋為,使用Java語言訪問訪問數據庫的一套接口集合。這是調用者(程序員)和實行者(數據庫廠商...
摘要:找到字節(jié)碼并創(chuàng)建一個對象。鏈接,檢驗字節(jié)碼,為字段分配存儲空間,解決其對他類的引用。初始化,如果有父類則初始化父類,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化區(qū)塊直到第一次訪問靜態(tài)成員時初始化才執(zhí)行。如果成員不是編譯時常量由初始化器賦值,也會引起初始化。 有兩種形式在運行時獲取類型信息: 傳統(tǒng)的RTTI 反射 Class對象 運行時的類型信息是通過Class對象表現(xiàn)的,它包含了類的信息。所有...
閱讀 1083·2021-09-22 15:19
閱讀 1697·2021-08-23 09:46
閱讀 2226·2021-08-09 13:47
閱讀 1405·2019-08-30 15:55
閱讀 1408·2019-08-30 15:55
閱讀 1974·2019-08-30 15:54
閱讀 2795·2019-08-30 15:53
閱讀 713·2019-08-30 11:03