![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5CZmNWO2QGZ2EjNjJ2Y1ITYiRWYkNzMmRjN4YzN1czMy8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.gif)
在java開發過程中,經常使用一些架構如Spring、SpringMvc來簡化開發操作,在web開發中,我們可以利用SpringMvc快速的把請求參數和實體進行轉換,在使用資料庫操作的時候,可以使用Mybatis/hibernate等架構實作實體與db的轉換操作,這其中就是利用了java的反射機制實作的操作,是以java進階之旅一定會有反射一席之地,下面讓我們詳細學習一下Java反射相關的類與方法。
反射适合用在哪
首先我們先思考一個問題,反射适合使用在哪裡呢?從功能上看,反射似乎無所不能,幾乎所有的類,所有的屬性、方法、構造我們都能使用,但是我們細細思考一下,在實際開發中,并不是所有場景都需要使用反射擷取屬性或者方法進行操作,反而更多的使用執行個體.xxx方式操作,而當這些操作重複次數較多的時候,我們往往會考慮優化代碼,減少代碼備援,提高複用,比如實體建構指派等操作,這個時候往往是我們最需要複用的地方,是以我們可以大體認為反射主要使用在實體操作過程中。而在一般操作資料的過程中,我們的實體一般都是知道并且依賴于對應的資料類型的,比如:
1.根據類型new的方式建立對象
2.根據類型定義變量,類型可能是基本類型也可能是引用類型、類或者接口
3.将對應類型的對象傳遞給方法
4.根據類型通路對象的屬性,調用方法等操作
以上這些操作都是資料操作的過程中最常見也是最難複用優化的地方,而如果這裡的操作使用反射則可以實作動态的操作不同的類型的執行個體,通過調用反射入口類Class,擷取對應的屬性、構造、方法完成對應的操作,是以接下來我們先從Class入口開始學習。
擷取Class
學習過類和繼承實作原理的時候,我們都知道,每個已經加載的類在記憶體中都有一份對應的類資訊,而每個對象都有所屬類的引用。其中類資訊存放的類即為java.lang.Class。而所有類的基類Object中就有一個本地方法可以快速擷取到Class對象。
publicfinalnativeClass> getClass()
可以看到傳回的類型為Class,即為目前類的資訊,但是由于基類的子類并不明确,是以具體的類型這裡使用範型的方式傳回。
而getClass方法不僅僅可以使用在類中,針對于接口也可以使用,當然接口沒有具體的實作,是以不可能是接口的執行個體.getClass的方式擷取,這個時候我們就需要調用内置的.class屬性快速擷取類型:
Class<Comparable> cls = Comparable.class;
而在java中有一些特殊的類型,例如基本類型,還有void類型,這種類型無法直接建立執行個體,如果要擷取class,與接口的方式相同,如下:
//這裡可以看出來,基本類型的class即為對應的包裝類型
Class<Integer> intCls = int.class;
Class<Byte> byteCls = byte.class;
Class<Character> charCls = char.class;
Class<Double> doubleCls = double.class;
//void也是一種特殊的類型,傳回的也是對應的包裝類Void
Class<Void> voidCls = void.class;
而數組和枚舉作為java中的特殊實作類,擷取的class類型也是較為特殊的,數組具有次元特性,是以擷取的class類型同樣具有次元,即一維數組有一個,二維數組有兩個,每個次元都有對應的不同類型,而枚舉的class則是其中定義的每一個子集,如下:
String[] strArr = newString[10];
int[][] twoDimArr = newint[3][2];
int[] oneDimArr = newint[10];
Class extendsString[]> strArrCls = strArr.getClass();
Class extendsint[][]> twoDimArrCls = twoDimArr.getClass();
Class extendsint[]> oneDimArrCls = oneDimArr.getClass();
而通過上述方式擷取Class對象以後,我們就可以了解到關于類型的很多資訊,并且基于這些資訊可以擷取我們想要的詳細資訊,大緻可以分為以下幾類,包括名稱資訊、字段資訊、方法資訊、建立對象的方法、構造資訊與類型資訊等,接下來我們就分别學習這幾類資訊相關的内容。
Class名稱資訊
我們在開發過程中,擷取到Class以後,往往需要擷取對應的類名進行操作,比如比較類名,排除類名等操作,而在Class類中,提供了以下幾個方法擷取類名稱相關資訊:
publicString getName()
publicString getSimpleName()
publicString getCanonicalName()
publicPackage getPackage()
這裡可以看出分别能擷取四種不同的類名稱,那麼擷取的具體内容有什麼不同呢?我們先看一張羅列的表格資訊:
可以看出來各個不同類型的Class通過四種方法擷取的類名稱資訊完全不同,那麼為什麼會出現這麼大的差別呢?這裡我們需要注意的是:
getName()
getName()方法擷取的是标準的類名稱資訊,即Java内部真正的類型對應的名稱資訊(JVM真實的類名稱資訊),這裡我們可以看出來數組的getName()的結果為[I,這裡需要解釋一下,[表示的是數組,并且和次元有關系,如果是二維數組,那麼這裡就會是[[,而後面的I則是int類型在JAVA中真實的類型的簡寫,八大基本類型對應的類簡寫名稱如下所示:
基本類型 | 真實類名稱簡寫 |
---|---|
boolean | Z |
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
而這裡還有一點需要注意的是,如果是引用類型對象的數組,Class的真實類名結尾還會有一個分号;
getSimpleName()
getSimpleName()方法是jdk實作的用來快速擷取目前類的真實類名的路徑縮寫,即不帶包名的Class名稱。
getCanonicalName()
getCanonicalName()方法擷取到的即為java中的完整僞類名,即包名+getSimpleName()名稱的完整名稱,此方法擷取的Class名稱比較友好。
getPackage()
getPackage()則是java中預設實作的可以用來快速擷取目前Class所在包名的方法,此方法僅僅傳回類路徑的前置(包名),不包含類名。
Class字段資訊
擷取完Class的名稱資訊以後,我們開始關注如何通過Class類擷取類屬性資訊。我們知道在類中定義的靜态和執行個體變量都被稱為字段,在Class類中則是使用Field表示,位于java.lang.reflect包下,而在Class類中有如下方法可以擷取Field資訊:
publicField[] getFields()
publicField[] getDeclaredFields()
publicField getField(String name)
publicField getDeclaredField(String name)
可以看出來主要是分為兩類,一類傳回的是字段數組,一類傳回具體的字段,分别看下這兩類方法的作用:
getFields()/getDeclaredFields()
getFields()/getDeclaredFields()方法都是傳回的目前Class中所有的字段,其中包括來自父類的,但是這兩個方法在使用的時候有一定的差別,getFields()方法隻能傳回非私有的字段,而getDeclaredFields()則是傳回所有的字段,包括私有的字段,當然還需要借助setAccessible方法才能實作。
getField(String name)/getDeclaredField(String name)
getField(String name)/getDeclaredField(String name)這兩個方法都是通過字段名來擷取對應的字段資訊,通過命名我們不難發現,和上一組類似,getField(String name)方法隻能從所有的非私有的字段中查找目前名稱的字段資訊,getDeclaredField(String name)則是從所有的字段中查找對應名稱的字段資訊。
操作Field的常見方法
通過上述的四個方法我們可以輕松擷取到Class中對應的字段資訊,接下來我們隻要對這些資訊進行操作處理,即可完成我們要做的操作,而常用的操作Field的方法如下:
publicString getName()
publicboolean isAccessible()
publicvoid setAccessible(boolean flag)
publicObjectget(Object obj)
publicvoidset(Object obj, Object value)
getName()
getName()方法通過命名即可看出來,此方法可以擷取到目前Field的字段名:
isAccessible()
isAccessible()方法我們在上述getDeclaredFields()方法的時候曾經介紹過,如果需要擷取私有字段,需要setAccessible方法支援,此方法則是可以擷取是否擷取到setAccessible方法的支援,即是否支援擷取私有字段。
setAccessible(boolean flag)
setAccessible(boolean flag)則是通過設定boolean值确認目前反射擷取Field的操作中,是否檢查私有字段,設定為true,則不檢查,反射可以擷取到私有Field,設定為false則是檢查私有字段,反射不可擷取私有Field。
get(Object obj)/set(Object obj, Object value)
get(Object obj)/set(Object obj, Object value)方法我們都不陌生,這一對方法則是能對目前Field設定對應的值/擷取對應的值。
其他方法
除了上述常見的方法以外,開發過程中可能還會使用到一些其他操作Field的方法,搭配使用,可以實作更靈活的字段操作,方法如下:
//傳回目前字段的修飾符--public、private等
publicint getModifiers()
//傳回目前字段的類型--String等
publicClass> getType()
//通過目前方法擷取/指派基礎類型的字段值
publicvoid setBoolean(Object obj, boolean z)
publicboolean getBoolean(Object obj)
publicvoid setDouble(Object obj, double d)
publicdouble getDouble(Object obj)
//擷取目前字段上的注解,使用jpa或者mybatis-plus等架構的時候,會添加在字段上一些注解
publicextendsAnnotation> T getAnnotation(Class annotationClass)
publicAnnotation[] getDeclaredAnnotations()
Class方法資訊
擷取完Field字段資訊以後,往往我們還需要進行方法的操作,比如調用xx方法實作部分功能,這個時候就需要擷取方法資訊,而在Class中提供了很多操作方法資訊的方法,常見的如下:
//擷取所有的非私有方法,包括父類的非私有方法
publicMethod[] getMethods()
//擷取所有方法,包括私有方法和父類的非私有方法
publicMethod[] getDeclaredMethods()
//從目前Class的所有public方法中查找對應名稱,并且參數清單相同的方法(包括父類非私有方法)
//如果查找不到會抛出NoSuchMethodException異常
publicMethod getMethod(String name, Class>... parameterTypes)
//從目前Class的所有方法包括父類的非私有方法中查找對應名稱并且參數清單相同的方法,如果查找不到會抛出NoSuchMethodException異常
publicMethod getDeclaredMethod(String name, Class>... parameterTypes)
而當我們通過上述方法擷取到Method對象後,即可操作此對象完成Method方法調用等,而Method資訊包含如下内容:
//擷取目前Method的名稱
publicString getName()
//是否忽略檢查機制,允許調用私有的Method,如果設定為true,則忽略檢查,允許調用私有方法,設定false則使用檢查機制,不允許操作私有方法
publicvoid setAccessible(boolean flag)
//調用指定obj執行個體對象的目前方法,并且依據參數調用正确的方法
publicObject invoke(Object obj, Object... args) throws
IllegalAccessException, Illegal-ArgumentException, InvocationTargetException
反射建立執行個體與構造方法
當我們拿到了字段資訊和方法資訊以後,這兒時候我們基本已經可以操作這些完成很多常見的功能了,但是除此之外,日常開發中還會遇到構造執行個體的頻繁操作,如果能反射建立執行個體就好了,是以Class中提供了建構執行個體的方法,并且提供了操作Class的構造方法,如下:
//擷取目前Class的所有public構造方法清單
publicConstructor>[] getConstructors()
//擷取目前Class中所有構造方法的清單,包含private的構造
publicConstructor>[] getDeclaredConstructors()
//根據參數清單查找目前符合的非私有構造方法,不滿足的情況抛出NoSuchMethodException異常
publicConstructor getConstructor(Class>... parameterTypes)
//根據參數清單查找目前所有構造中符合的方法,不滿足的情況抛出NoSuchMethodException異常
publicConstructor getDeclaredConstructor(Class>... parameterTypes)
通過調用擷取的構造方法可以完成類執行個體的加載建構,同樣的我們也可以使用如下方法快速建構類執行個體:
public T newInstance() throwsInstantiationException, IllegalAccessException
舉個簡單的例子:
Map<String,Integer> map = HashMap.class.newInstance();//通過Class>.newInstance建構類執行個體
map.put("hello", 123);
Class的類型資訊與聲明資訊
通過上述的學習,我們已經了解到Class類是個神奇的存在,既可以擷取名稱,也可以操作字段和方法,那麼我們不禁疑惑,Class代表的類型到底是普通的類,還是内部類,還是基礎類型或者數組呢?其實Class是一個類型的集合,每一個Class的類型取決于原類型,在Class方法中也提供了如下方法輔助判斷Class的類型資訊,如下:
//Class類型是否為數組
publicnativeboolean isArray()
Class類型是否為基本資料類型--包裝類
publicnativeboolean isPrimitive()
//Class類型是否為接口
publicnativeboolean isInterface()
//Class類型是否為枚舉
publicboolean isEnum()
//Class類型是否為注解類型
publicboolean isAnnotation()
//Class類型是否為匿名内部類
publicboolean isAnonymousClass()
//Class類型是否為成員類,定義在方法外的Class
publicboolean isMemberClass()
//Class類型是否為本地類,即定義在方法内的Class,非匿名内部類
publicboolean isLocalClass()
除了Class本身的類型資訊,我們也可以根據以下方法擷取Class的聲明資訊、父類、接口等資訊,方法如下:
//擷取目前類的修飾符
publicnativeint getModifiers()
//擷取目前類的父類型資訊
publicnativeClass super T> getSuperclass()
//擷取目前類實作的所有的接口資訊
publicnativeClass>[] getInterfaces();
//擷取目前類申明的注解資訊數組
publicAnnotation[] getDeclaredAnnotations()
//擷取目前類中所有的注解資訊
publicAnnotation[] getAnnotations()
//根據注解的完整類名,查找到對應的注解資訊
publicextendsAnnotation> A getAnnotation(Class
數組與枚舉的反射
在使用反射過程中,除了日常的操作以外,有些時候我們還需要針對數組和枚舉類型的Class做一些反射操作,而數組類型的操作往往需要借助java.lang.reflect包下的Array類操作完成,主要方法如下:
//建立元素類型、元素長度指定的數組
publicstaticObject newInstance(Class> componentType, int length)
//建立多元度的數組,dimensions可連續傳遞多個,分别代表不同次元
publicstaticObject newInstance(Class> componentType, int... dimensions)
//擷取指定數組的對應索引的值
publicstaticnativeObject get(Object array, int index)
//指派給指定數組的對應索引下的值
publicstaticnativevoid set(Object array, int index, Object value)
//擷取數組長度
publicstaticnativeint getLength(Object array)
需要注意的是,在Array類中,數組使用Object而不是Object[]表示,這是為了友善處理多種類型的數組而設計的,因為在java中int[]、String[]等數組都不可以與Object[]互相轉化,但是卻可以轉為Object,例如:
int[] intArr = (int[])Array.newInstance(int.class, 10);
String[] strArr = (String[])Array.newInstance(String.class, 10);
除了數組類型,在開發中,尤其是遇到固定常量類型的時候,往往選擇使用枚舉類來實作操作,但是在反射中,當我們需要查找枚舉類型的時候,Class類提供了如下方法擷取我們枚舉類中定義的所有的常量,進而可以實作枚舉相關的反射操作。
//擷取目前枚舉類型Class的所有定義的枚舉常量
public T[] getEnumConstants()
萬水千山總是情,點個在看行不行