java的反射用不好試試內(nèi)省?
Java的內(nèi)省機制是什么?
內(nèi)?。↖ntrospection )在心理學(xué)中,它是心理學(xué)基本研究方法之一。內(nèi)省法又稱自我觀察法。它是發(fā)生在內(nèi)部的,我們自己能夠意識到的主觀現(xiàn)象。也可以說是對于自己的主觀經(jīng)驗及其變化的觀察。正因為它的主觀性,內(nèi)省法自古以來就成為心理學(xué)界長期的爭論。爭論于它是否客觀,是否可靠。另外內(nèi)省也可看作自我反省,也是儒家強調(diào)的自我思考。從這個角度說它可以應(yīng)用于計算機領(lǐng)域,例如Java內(nèi)省機制和cocoa內(nèi)省機制。
Java語言內(nèi)?。↖ntrospector)是Java語言對Bean類屬性、事件的一種缺省處理方法。例如類A中有屬性name,那我們可以通過getName,setName來得到其值或者設(shè)置新的值。通過getName/setName來訪問name屬性,這就是默認的規(guī)則。Java中提供了一套API用來訪問某個屬性的getter/setter方法,通過這些API可以使你不需要了解這個規(guī)則(但你最好還是要搞清楚),這些API存放于包java.beans中。一般的做法是通過類Introspector來獲取某個對象的BeanInfo信息,然后通過BeanInfo來獲取屬性的描述器(PropertyDescriptor),通過這個屬性描述器就可以獲取某個屬性對應(yīng)的getter/setter方法,然后我們就可以通過反射機制來調(diào)用這些方法。
以上就是百科的解釋。Java的內(nèi)省最終是用Java的反射實現(xiàn)的。那為什么不直接用反射,要使用內(nèi)省呢?
使用內(nèi)省替代直接使用反射可以防止破壞類的封裝
我們定義一個人的類型,其中包括年齡和是否成年兩個屬性。在修改年齡屬性的時候會同時修改是否成年的屬性。我們假設(shè)18歲和18歲以上就是成年,否則就是未成年。
import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; class Person { /** * 18歲成年 */ private static final int ADULT_AGE = 18; /** * 年齡 */ private int age; /** * 是否成年 */ private boolean adult; public int getAge() { return age; } public void setAge(int age) { this.age = age; this.adult = age >= ADULT_AGE; } public boolean isAdult() { return adult; } public String toString() { return MessageFormat.format("age:{0},adult:{1}", age, adult); } } /** * @author 二當家的白帽子 https://le-yi.blog.csdn.net/ */ public class Test { /** * 利用反射修改對象屬性 * @param o * @param fieldName * @param value * @throws NoSuchFieldException * @throws IllegalAccessException */ public static void changeObjectFieldByReflection(Object o, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = o.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(o, value); } /** * 利用內(nèi)省修改對象屬性 * @param o * @param fieldName * @param value * @throws NoSuchFieldException * @throws IllegalAccessException */ public static void changeObjectFieldByIntrospector(Object o, String fieldName, Object value) throws IntrospectionException, InvocationTargetException, IllegalAccessException { PropertyDescriptor pd = new PropertyDescriptor(fieldName, o.getClass()); pd.getWriteMethod().invoke(o, value); } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IntrospectionException, InvocationTargetException { Person p = new Person(); changeObjectFieldByReflection(p, "age", 20); System.out.println("反射修改屬性破壞類的封裝,使其內(nèi)部狀態(tài)錯誤:"); System.out.println(p); changeObjectFieldByIntrospector(p, "age", 18); System.out.println("內(nèi)省修改屬性未破壞類的封裝:"); System.out.println(p); } }
可以看到,反射由于是直接修改屬性,所以破壞了類中封裝的邏輯(20歲卻不是成年)。
而內(nèi)省由于修改屬性還是調(diào)用了set方法,也就是說和正常修改對象屬性調(diào)用了相同的方法,所以類的封裝性不會遭到破壞。
當然由于內(nèi)省其實本質(zhì)也是反射,可以說是封裝了反射,所以如果反射用的正確,也是安全的,我們可以根據(jù)屬性名去獲取相應(yīng)的set和get方法,然后再去調(diào)用,但是這種情況下內(nèi)省使用起來就更方便,畢竟沒有必要重復(fù)發(fā)明一個車輪子,圓形輪子已經(jīng)是很多年很多年智慧的結(jié)晶了。
那么問題來了,既然內(nèi)省就是調(diào)用set和get方法,那我為什么不直接調(diào)用set和get方法,而要使用內(nèi)省呢?
使用內(nèi)省也一樣可以寫出通用的工具
既然內(nèi)省可以動態(tài)獲取信息,那就和反射一樣,可以實現(xiàn)出通用工具或者框架哦。我們這里實現(xiàn)一個可以拷貝任意類型兩個對象的屬性值的工具方法。
import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.MessageFormat; class Person { /** * 18歲成年 */ private static final int ADULT_AGE = 18; /** * 名字 */ private final String name; /** * 身高 */ private int height; /** * 年齡 */ private int age; /** * 是否成年 */ private boolean adult; public Person(String name) { this.name = name; } public String getName() { return name; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getAge() { return age; } public void setAge(int age) { this.age = age; this.adult = age >= ADULT_AGE; } public boolean isAdult() { return adult; } public String toString() { return MessageFormat.format("name:{0},height:{1},age:{2},adult:{3}", name, height, age, adult); } } /** * @author 二當家的白帽子 https://le-yi.blog.csdn.net/ */ public class Test { /** * 將orig的可讀屬性值拷貝到dest的可寫屬性中 * @param dest * @param orig * @param <T> * @throws IntrospectionException * @throws InvocationTargetException * @throws IllegalAccessException */ public static <T> void copyProperties(T dest, T orig) throws IntrospectionException, InvocationTargetException, IllegalAccessException { BeanInfo beanInfo = Introspector.getBeanInfo(orig.getClass()); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { Method rm = pd.getReadMethod(); Method wm = pd.getWriteMethod(); if (rm != null && wm != null) { Object value = rm.invoke(orig); wm.invoke(dest, value); } } } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IntrospectionException, InvocationTargetException { Person p2 = new Person("二當家的"); p2.setAge(18); p2.setHeight(180); System.out.println(p2); Person p1 = new Person("大當家的"); System.out.println(p1); System.out.println("將二當家的可讀屬性值拷貝給大當家的可寫屬性:"); copyProperties(p1, p2); System.out.println(p1); } }
可以看到,名字沒有被拷貝,其他的屬性值都順利拷貝了。這也是我們期望的結(jié)果。
內(nèi)省很好的保證了類的封裝性,同時又具有動態(tài)獲取對象屬性,和動態(tài)修改對象屬性的能力。
總結(jié)
和反射一樣,一般的程序可能也用不到寫內(nèi)省的代碼。但是像apache的beanutils這樣方便的工具,如果沒有反射也沒有內(nèi)省,我真的想不出如何實現(xiàn)呢。哪怕永遠不需要用內(nèi)省,了解機制對我們都有著莫大的好處。
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Maven?繼承父工程時的relativePath標簽詳細解析
這篇文章主要介紹了Maven?繼承父工程時的relativePath標簽解析,通過本文學(xué)習你需要注意子模塊想要用父模塊pom中的版本,請注意配置relativePath屬性,需要的朋友可以參考下2022-12-12SSH框架網(wǎng)上商城項目第12戰(zhàn)之添加和更新商品功能
這篇文章主要介紹了SSH框架網(wǎng)上商城項目第12戰(zhàn)之添加和更新商品功能的實現(xiàn)代碼,感興趣的小伙伴們可以參考一下2016-06-06如何通過Java監(jiān)聽MySQL數(shù)據(jù)的變化
對于二次開發(fā)來說,很大一部分就找找文件和找數(shù)據(jù)庫的變化情況,下面這篇文章主要給大家介紹了關(guān)于如何通過Java監(jiān)聽MySQL數(shù)據(jù)的變化的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-03-03