java swing jbutton_Java 反射
點擊上方“凌天實驗室”,“星標或置頂公眾號”
漏洞、技術還是其他,我都想第一時間和你分享
1前? 言
本章為新手向零基礎 Java 反射學習筆記。
截取部分本實驗室發起的項目javaweb-sec。
Gitbook地址:https://javasec.org/
2反射的基本定義
官方文檔:https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html
Reflection(反射)賦予了 Java 代碼從已加載的類中發現成員變量(Fields)、成員方法(Methods)和構造方法(Constructors)的能力,并可以在安全限制內使用這些反射得到的成員變量、成員方法、構造方法來操作他們對應的底層對象。
簡而言之,你可以在運行狀態中通過反射機制做到:
對于任意一個類,都能夠知道這個類的所有屬性和方法;
對于任意一個對象,都能夠調用它的任意一個方法和屬性。
這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
不得不說,反射是一種十分具有 "Hacker" 精神的機制。
3反射的基礎
從源代碼到程序運行,大致經歷的步驟如下:
創建源文件后,程序會被編譯器編譯為后綴名為?.class?的文件;
通過類加載器系統將字節碼.class加載入 JVM 的內存中,類的加載是通過 ClassLoader 及其子類來完成的;在加載階段,虛擬機要完成三件事情:通過一個類的全限定名來獲取其定義的二進制字節流,將這個字節流所代表的的靜態儲存結構轉化為方法區的運行時數據結構,在 Java 堆中生成一個代表這個類的?java.lang.Class?對象,作為對方法區中這些數據的訪問入口。在加載的過程中將使用雙親委派模型進行類的加載;
對字節碼進行驗證;
解析類、接口、字段,是虛擬機將常量池中的符號引用轉化為直接引用的過程;
類初始化,這里需要注意的是,在使用?Java.lang.reflect?包的方法對類進行反射調用時,如果類還沒有進行過初始化,則需要先觸發其初始化;
執行。
可以看到,在加載的過程中,當一個 class 被加載,或當加載器(classloader)的?defineClass()被 JVM 調用時,JVM 將自動產生一個 Class 對象,并且這個對象會保存在同名的?.class?文件里,當我們 new 一個新對象或者引用一個靜態成員變量時,JVM 中的加載器系統會將對應的 Class 對象加載到 JVM 中,然后 JVM 再根據這個類型信息相關的 Class 對象創建我們需要實例對象或者提供靜態變量的引用值。
因此,這些在程序加載過程中產生的類的 Class 對象就是反射的基礎。
Class 類位于包?java.lang?下:
public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type,AnnotatedElement可以看到,這是個 final 類,實現了 4 個接口。
這個類十分特殊,它同樣的繼承自 Object ,它的實例能用來表達 Java 程序運行時的 classes 和 interfaces,也能用來表達 enum、array、Java 基礎類型(boolean、byte、char、short、int、long、double、float)以及關鍵詞 void。
Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的?defineClass?方法自動構造的。
4獲取 Class 對象的方法
通過上面的了解,我們知道,如果想使用反射,必須得獲得 Class 對象。
除了?java.lang.reflect.ReflectPermission?以外,java.lang.reflect?中的其他類都沒有 public 的構造函數,也就是說要得到這些類,我們必須通過 Class 。
下面列舉了能夠獲取 Class 對象的方法:
1. Object.getClass()
第一種方法是通過類的實例來獲取對應的 Class 對象。
byte[] bytes = new byte[1024];Class> c = bytes.getClass();
但是對于基礎數據類型不能使用這種方式。
2. 使用.Class 語法
通過類的類型獲取 Class 對象,基本類型同樣可以使用這種方法。
Class> c = boolean.class;Class> c = String.class;
3. Class.forName()
通過類的全限定名獲取Class對象, 基本類型無法使用此方法。
try {Class> c = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
對于數組比較特殊:
Class> doubleArray = Class.forName("[D"); //相當于double[].classClass> cStringArray = Class.forName("[[Ljava.lang.String;"); //相當于String[][].class
如果在調用Class.forName()方法時,沒有在編譯路徑下(classpath)找到對應的類,那么將會拋出ClassNotFoundException。
4. 基礎類型封裝類型 TYPE
基本類型和 void 類型的包裝類可以使用 TYPE 字段獲取。
Class c = Double.TYPE; //等價于 double.classClass c = Void.TYPE;
5. 能夠返回 Class 類型的方法
另外還有一些反射方法可以獲取 Class 對象,但前提是你已經獲取了一個 Class 對象。比如說你已經獲取了一個類的 Class 對象,就可以通過Class.getSuperclass()方法獲取這個類的父類的 Class 對象。
Class> c = javax.swing.JButton.class.getSuperclass();類似能夠返回 Class 對象的方法還有:
Class.getClasses()
Class.getDeclaredClasses()
Class.getDeclaringClass()
Class.getEnclosingClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()
還有部分類能夠返回 Class 對象,但基本上都是調用?Class.forName(),此處不再列舉。
5從 Class 中獲取信息
在獲取了 Class 對象后,就可以通過 Class 類提供的方法來獲取其中的信息和進行操作了。
1. 獲取類的信息
| 類名 | String getName() |
| 類名簡稱 | String getSimpleName() |
| 規范化類名 | String getCanonicalName() |
| 類加載器 | ClassLoader getClassLoader() |
| 泛型的參數類型 | TypeVariable>[] getTypeParameters() |
| 直接繼承的父類 | Class super T> getSuperclass() |
| 直接繼承的父類(包含泛型信息) | Type getGenericSuperclass() |
| 包含的方法 | Method getMethod(String name, Class>... parameterTypes) |
| 內部類 | Class>[] getDeclaredClasses() |
| 構造器 | Constructor getConstructor(Class>... parameterTypes) |
| 包含的屬性 | Field getField(String name) |
| 修飾符 | int getModifiers() |
| 類的標記 | Object[] getSigners() |
| 數組的 Class 對象 | Class> getComponentType() |
| 所在包 | Package getPackage() |
| 所實現的接口 | Class>[] getInterfaces() |
| 所實現的接口(包含泛型信息) | Type[] getGenericInterfaces() |
| 包含的Annotation | A getAnnotation(Class annotationClass) |
在 Class 類中,可以看到類似 getEnclosing*、getDeclared* 與 getDeclaringClass,可以理解為在不同”域“中獲取信息,由于篇幅的原因不再進行羅列:
get*:返回當前類和繼承層次中的所有父類的成員
getEnclosing*:返回內部或匿名的封閉成員
getDeclared*:返回當前類中的成員(不包含父類)
getDeclaringClass:返回當前類聲明所在的類
2. 判斷類本身信息的方法
Class 類提供了一些判斷類本身信息的方法,通常命名為 isXxxxx ,返回類型均為 boolean。
| 是否為注解類型 | boolean isAnnotation() |
| 是否使用了該Annotation修飾 | boolean isAnnotationPresent(Class extends Annotation> annotationClass) |
| 是否為匿名類 | boolean isAnonymousClass() |
| 是否為數組類型 | boolean isArray() |
| 是否為枚舉類型 | boolean isEnum() |
| 判斷兩個類的是否關聯 | boolean isAssignableFrom() |
| 是否為接口 | boolean isInterface() |
| obj 是否是該 Class 的實例 | boolean isInstance(Object obj) |
| 該類是否為局部類 | boolean isLocalClass() |
| 該類是否為成員類 | boolean isMemberClass() |
| 是否為基礎類型 | boolean isPrimitive() |
| 是否由Java編譯器引入 | boolean isSynthetic() |
3. 獲取構造方法
Class 類提供了四個 public 方法,用于獲取某個類的構造方法。
| Constructor getConstructor(Class[] params) | 根據構造函數的參數,返回一個具體的具有public屬性的構造函數 |
| Constructor getConstructors() | 返回所有具有public屬性的構造函數數組 |
| Constructor getDeclaredConstructor(Class[] params) | 根據構造函數的參數,返回一個具體的構造函數(不分public和非public屬性) |
| Constructor getDeclaredConstructors() | 返回該類中所有的構造函數數組(不分public和非public屬性) |
如果想要反射出無參數的構造方法,可以直接使用?newInstanse()?方法創建新實例,因為該方法的本質即調用類的無參數構造方法。
4. 獲取類的成員方法
| Method getMethod(String name, Class[] params) | 根據方法名和參數,返回一個具體的具有public屬性的方法 |
| Method[] getMethods() | 返回所有具有public屬性的方法數組 |
| Method getDeclaredMethod(String name, Class[] params) | 根據方法名和參數,返回一個具體的方法(不分public和非public屬性) |
| Method[] getDeclaredMethods() | 返回該類中的所有的方法數組(不分public和非public屬性) |
5. 獲取類的成員屬性
| Field getField(String name) | 根據變量名,返回一個具體的具有public屬性的成員變量 |
| Field[] getFields() | 返回具有public屬性的成員變量的數組 |
Field getDeclaredField(String name) | 根據變量名,返回一個成員變量(不分public和非public屬性) |
| Field[] getDelcaredFields() | 返回所有成員變量組成的數組(不分public和非public屬性) |
上面列舉的部分方法中還存在部分方法的重載方法,不再贅述,按需使用。
6反射操作
能夠反射得到類、屬性、方法之后,應該如何操作呢?
首先我們先看反射的包?java.lang.reflect?下的 Member 接口,顧名思義,這是一個標識為成員的接口,這個接口有若干個實現:
其中我們首先關注的是:
java.lang.reflect.Field?:對應類變量。
java.lang.reflect.Method?:對應類方法。
java.lang.reflect.Constructor?:對應類構造函數。
反射就是通過這三個類才能在運行時改變對象狀態。
而 Class 對象的?getXXX()?方法返回的成員方法,成員屬性,構造方法就是 reflect 包中相對應的類。
其中 Method 類和 Constructor 類繼承自 Executable 類,Executable 類實現了 Member 接口,而 Field 類則直接實現了Member 接口。
從 JDK 1.8 開始,java.lang.reflect.Executable.getParameters?為我們提供了獲取普通方法或者構造方法的名稱的能力,因此 Method 類和 Constructor 類也具有這個能力。
1. Field
每個成員變量有類型和值。java.lang.reflect.Field?為我們提供了獲取當前對象的成員變量的類型,和重新設值的方法。
具體方法如下:
獲取變量的類型:
| Class> getType() | 返回這個變量的類型 |
| Type getGenericType() | 如果當前屬性有簽名屬性就返回,否則返回getType() |
這里再復習一下類型,類中的變量分為兩種類型:基本類型和引用類型:
基本類型( 8 種)
整數:byte, short, int, long
浮點數:float, double
字符:char
布爾值:boolean
引用類型
類,枚舉,數組,接口都是引用類型
java.io.Serializable 接口,基本類型的包裝類(比如?java.lang.Double)也是引用類型
獲取和修改成員變量的值:
拿到一個對象后,我們可以在運行時修改它的成員變量的值,對運行時來說,反射修改變量值的操作和類中修改變量的結果是一樣的。
基本類型的獲取方法:
| byte getByte(Object obj) | 獲取一個靜態或實例 byte 字段的值 |
| int getInt(Object obj) | 獲取 int 類型或另一個通過擴展轉換可以轉換為 int 類型的基本類型的靜態或實例字段的值 |
| short getShort(Object obj) | 獲取 short 類型或另一個通過擴展轉換可以轉換為 short 類型的基本類型的靜態或實例字段的值 |
| long getLong(Object obj) | 獲取 long 類型或另一個通過擴展轉換可以轉換為 long 類型的基本類型的靜態或實例字段的值 |
| float getFloat(Object obj) | 獲取 float 類型或另一個通過擴展轉換可以轉換為 float 類型的基本類型的靜態或實例字段的值 |
| double getDouble(Object obj) | 獲取 double 類型或另一個通過擴展轉換可以轉換為 double 類型的基本類型的靜態或實例字段的值 |
| boolean getBoolean(Object obj) | 獲取一個靜態或實例 boolean 字段的值 |
| char getChar(Object obj) | 獲取 char 類型或另一個通過擴展轉換可以轉換為 char 類型的基本類型的靜態或實例字段的值 |
基本類型的設置方法:
| void setByte(Object obj, byte b) | 將字段的值設置為指定對象上的一個 byte 值 |
| void setShort(Object obj, short s) | 將字段的值設置為指定對象上的一個 short 值 |
| void setInt(Object obj, int i) | 將字段的值設置為指定對象上的一個 int 值 |
| void setLong(Object obj, long l) | 將字段的值設置為指定對象上的一個 long 值 |
| void setFloat(Object obj, float f) | 將字段的值設置為指定對象上的一個 float 值 |
| void setDouble(Object obj, double d) | 將字段的值設置為指定對象上的一個 double 值 |
| void setBoolean(Object obj, boolean z) | 將字段的值設置為指定對象上的一個 boolean 值 |
| void setChar(Object obj, char c) | 將字段的值設置為指定對象上的一個 char 值 |
引用類型的獲取和設置方法:
| Object get(Object obj) | 返回指定對象上此 Field 表示的字段的值 |
| void set(Object obj, Object value) | 將指定對象變量上此 Field 對象表示的字段設置為指定的新值 |
2. Method
繼承的方法(包括重載、重寫和隱藏的)會被編譯器強制執行,這些方法都無法反射。因此,反射一個類的方法時不考慮父類的方法,只考慮當前類的方法。
每個方法都由修飾符、返回值、參數、注解和拋出的異常組成。
java.lang.reflect.Method?方法為我們提供了獲取上述部分的 API。
重寫 Object 類的方法不再描述,重寫 Executable 的方法將在后面部分描述,在此只列舉一些 Method 類自己的方法。
| Object getDefaultValue() | 返回由此方法實例表示的注釋成員的默認值 |
| Type getGenericReturnType() | 獲取目標方法返回類型對應的 Type 對象 |
| Class> getReturnType() | 獲取目標方法返回類型對應的 Class 對象 |
| boolean isBridge() | 判斷是否是橋接方法 |
| boolean isDefault() | 如果此方法是默認方法,則返回 true ; 否則返回 false |
| Object invoke(Object obj, Object... args) | 使用反射執行方法 |
首先來看一下?getReturnType()?和?getGenericReturnType()?的異同。
getReturnType()返回類型為 Class,getGenericReturnType()返回類型為 Type ; Class 實現 Type 。
返回值為普通簡單類型如 Object , int , String 等,getGenericReturnType()返回值和getReturnType()一樣。
例如?public String function1()那么各自返回值為:getReturnType() : class java.lang.StringgetGenericReturnType() : class java.lang.String
返回值為泛型
例如public T function2()那么各自返回值為:getReturnType() : class java.lang.ObjectgetGenericReturnType() : T
返回值為參數化類型
例如public Class function3()那么各自返回值為:getReturnType() : class java.lang.ClassgetGenericReturnType() : java.lang.Class
其實反射中所有形如getGenericXXX()的方法規則都與上面所述類似。
然后就是非常重要的invoke()方法,
public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException
第一個 Object 參數代表的是對應的 Class 對象實例,第二個參數是可變形參,能夠接受多個參數。
這里需要注意的是,invoke 方法的兩個參數均為?Object?類型。
簡單來說,通過?invoke()?方法可以讓我們調用反射得到的類的方法。那么具體是怎么實現的呢?
invoke過程圖解:
此處不進行過多描述,有興趣可以跟一下源碼。
3. Constructor
同樣地,我們也只列舉 Constructor 自己的 public 方法:
| T newInstance(Object ... initargs) | 調用構造方法創建新實例 |
方法使用此 Constructor 對象表示的構造函數,使用指定的初始化參數來創建和初始化構造函數的聲明類的新實例。個別參數自動展開以匹配原始形式參數,原始參考參數和參考參數都需要進行方法調用轉換。
通過 Class 對象也可以創建新實例,但是兩者的異同在于:
Class.newInstance()只能反射無參數的構造器,也就是使用無參數構造方法創建新實例,而?Constructor.newInstance()可以反射任何構造器;
Class.newInstance()?需要構造器可見(visible),Constructor.newInstance()可以反射私有構造器;
Class.newInstance()對于捕獲或者未捕獲的異常均由構造器拋出,Constructor.newInstance()通常會把拋出的異常封裝成InvocationTargetException拋出;
因此,還是建議直接使用?Constructor.newInstance()?反射構造方法,幾乎全部的框架都是使用此種模式進行反射的。
4. Executable
Executable 抽象類繼承 AccessibleObject 類,實現了 Member 接口,并有兩個子類分別為 Constructor 和 Method,如下:
該類中定義了多個方法,能夠在通過反射獲得的成員方法或構造方法中使用:
該抽象類聲明的方法有:
| Class>[] getParameterTypes() | 按照聲明順序返回 Class 對象的數組,這些對象描述了此 Method/Constructor 對象所表示的方法的形參類型。 |
| Class>[] getExceptionTypes() | 返回 Class 對象的數組,這些對象描述了聲明將此 Method/Constructor 對象表示的底層方法拋出的異常類型。 |
| Type[] getGenericParameterTypes() | 按照聲明順序返回 Type 對象的數組,這些對象描述了此 Method/Constructor 對象所表示的形參類型的。 |
| Type[] getGenericExceptionTypes() | 返回 Type 對象數組,這些對象描述了聲明由此 Method/Constructor 對象拋出的異常的類型。 |
| String toGenericString() | 返回描述此 Method/Constructor 的字符串,包括類型參數。 |
| Annotation[][] getParameterAnnotations() | 返回表示按照聲明順序對此 Method/Constructor 對象所表示方法的形參進行注釋的那個數組的數組。 |
| AnnotatedType getAnnotatedReturnType() | 返回一個AnnotatedType對象,該對象表示使用一個類型來指定由該可執行文件表示的方法/構造函數的返回類型 |
| Type[] getGenericExceptionTypes() | 返回一個AnnotatedType對象數組,這些對象表示使用類型來指定由該可執行文件表示的方法/構造函數聲明的異常 |
| AnnotatedType getAnnotatedReceiverType() | 返回一個AnnotatedType對象,該對象表示使用一個類型來指定該可執行對象表示的方法/構造函數的接收者類型 |
| AnnotatedType[] getAnnotatedParameterTypes() | 返回一個AnnotatedType對象數組,這些對象表示使用類型來指定由該可執行文件表示的方法/構造函數的形式參數類型 |
| int getParameterCount() | 獲取參數的個數(無論是顯式聲明的還是隱式聲明的或不聲明的 |
| Parameter[] getParameters() | 返回一個參數對象數組,該數組表示該方法對象的所有參數 |
| boolean isVarArgs() | 是否是可變參數 |
5. AccessibleObject
在?java.lang.reflect?包中,存在一個 AccessibleObject 類,細心的同學已經發現了,該類是Method、Field、Constructor 類的基類,它提供了標記反射對象的能力,以抑制在使用時使用默認 Java 語言訪問控制檢查,從而能夠任意調用被私有化保護的方法、域和構造函數。
此類實現了 AnnotatedElement 接口,主要是注解相關的操作。
聲明的方法:
| boolean isAccessible() | 獲取此對象的accessible標志的值(布爾類型) |
| void setAccessible(AccessibleObject[] array, boolean flag) | 使用單一安全檢查來設置對象數組的可訪問標志的一個方便的方法(為了效率),靜態方法 |
| void setAccessible(boolean flag) | 將對象的可訪問標志設置為指示的布爾值 |
通過調用?setAccessible()?方法會關閉反射訪問檢查,這行代碼執行之后不論是私有的、受保護的以及包訪問的作用域,你都可以在任何地方訪問,即使你不在他的訪問權限作用域之內。
當isAccessible()的結果是 false 時不允許通過反射訪問該字段;
當該字段時 private 修飾時isAccessible()得到的值是 false ,必須要改成 true 才可以訪問;
所以?f.setAccessible(true)得作用就是讓我們在用反射時訪問私有變量。
所以你其實并不是把 private 方法改成了 public ,也沒有更改任何的權限,你只是關閉了反射訪問的檢查,這種情況下,其實可以導致一些效率上的提升,與此同時帶來的就是安全性的下降。
6. AnnotatedElement
此接口位于包?java.lang.reflect?下。
這個接口的對象代表了在當前 JVM 中的一個“被注解元素”。在 Java 語言中,所有實現了這個接口的“元素”都是可以“被注解的元素”。
使用這個接口中聲明的方法可以讀取(通過 Java 的反射機制)“被注解元素”的注解。
這個接口中的所有方法返回的注解都是不可變的、并且都是可序列化的。這個接口中所有方法返回的數組可以被調用者修改,而不會影響其返回給其他調用者的數組。
此類具有以下的實現類:
AccessibleObject(可訪問對象,如:方法、構造器、屬性等)
Class
Constructor
Executable(可執行的,如構造器和方法)
Field(屬性,類中屬性的類型)
Method(方法,類中方法的類型)
Package(包)
Parameter(參數,主要指方法或函數的參數,其實是這些參數的類型)
接口聲明的方法有:
| boolean isAnnotationPresent(Class extends Annotation> annotationClass) | 如果指定類型的注解出現在當前元素上,則返回 true ,否則將返回false。這種方法主要是為了方便地訪問一些已知的注解。 |
| T getAnnotation(Class annotationClass) | 如果在當前元素上存在參數所指定類型(annotationClass)的注解,則返回對應的注解,否則將返回 null 。 |
| Annotation[] getAnnotations() | 返回在這個元素上的所有注解。如果該元素沒有注釋,則返回值是長度為0的數組。該方法的調用者可以自由地修改返回的數組;它不會對返回給其他調用者的數組產生影響。 |
| T[] getAnnotationsByType(Class annotationClass) | 返回與該元素相關聯的注解。如果沒有與此元素相關聯的注解,則返回值是長度為0的數組。這個方法與getAnnotation(Class)的區別在于,該方法檢測其參數是否為可重復的注解類型( JLS 9.6 ),如果是,則嘗試通過“ looking through ”容器注解來查找該類型的一個或多個注解。該方法的調用者可以自由地修改返回的數組;它不會對返回給其他調用者的數組產生影響。 |
| T getDeclaredAnnotation(Class annotationClass) | 如果參數中所指定類型的注解是直接存在于當前元素上的,則返回對應的注解,否則將返回null。這個方法忽略了繼承的注解。(如果沒有直接在此元素上顯示注釋,則返回null。) |
| T[] getDeclaredAnnotationsByType(Class annotationClass) | 如果參數中所指定類型的注解是直接存在或間接存在于當前元素上的,則返回對應的注解。這種方法忽略了繼承的注解。如果沒有直接或間接地存在于此元素上的指定注解,則返回值是長度為0的數組。這個方法和getDeclaredAnnotation(Class)的區別在于,這個方法檢測它的參數是否為可重復的注釋類型( JLS 9.6 ),如果是,則嘗試通過“ looking through ”容器注解來查找該類型的一個或多個注解。該方法的調用者可以自由地修改返回的數組;它不會對返回給其他調用者的數組產生影響。 |
| Annotation[] getDeclaredAnnotations() | 返回直接出現在這個元素上的注解。這種方法忽略了繼承的注解。如果在此元素上沒有直接存在的注解,則返回值是長度為0的數組。該方法的調用者可以自由地修改返回的數組;它不會對返回給其他調用者的數組產生影響。 |
7. Member
Member 接口標識類或接口的所有公共成員的集合,包括繼承的成員。Constructor / Filed / Method 類都直接或間接的繼承此類。
Member 接口中定義的方法為:
| Class> getDeclaringClass() | 返回表示該類或接口的 Class 對象 |
| String getName() | 返回此成員表示的基礎成員或構造函數的簡單名稱 |
| int getModifiers() | 以整數形式返回此成員表示的成員或構造函數的 Java 語言修飾符 |
| boolean isSynthetic() | 是否由 Java 編譯器引入 |
8. 反射操作總結
通過以上知識的學習,可以知道:
AnnotatedElement?接口提供獲取注解相關能力。
Member?接口提供通用成員屬性獲取能力。
GenericDeclaration?接口提供給 Class 獲取泛型類型的能力。
Executable?抽象類提供獲取可執行對象相關信息的能力。
AccessibleObject?抽象類提供判斷可更改可訪問標識的能力。
Constructor、Method、Field各自基礎或實現上述接口和抽象類,賦予了他們獲取相關信息的能力。
以上類或接口之間的關系為:
9. 數組和枚舉
數組和枚舉也是對象,但是在反射中,對數組和枚舉的創建、訪問和普通對象有些不同,所以 Java 反射為數組和枚舉提供了一些特定的API接口。
數組
數組類型
數組類型:數組本質是一個對象,所以它也有自己的類型。例如對于int[] intArray,數組類型為class [I。數組類型中的[個數代表數組的維度,例如[代表一維數組,[[代表二維數組;[后面的字母代表數組元素類型,I代表int,一般為類型的首字母大寫( long 類型例外,為 J )。
Class> c = field.getType();
//判斷該變量是否為數組
if (c.isArray()) {
//獲取數組的元素類型
c.getComponentType()
}
創建和初始化數組
Java反射為我們提供了java.lang.reflect.Array類用來創建和初始化數組。
//創建數組, 參數componentType為數組元素的類型,后面不定項參數的個數代表數組的維度,參數值為數組長度Array.newInstance(Class> componentType, int... dimensions)
//設置數組值,array為數組對象,index為數組的下標,value為需要設置的值
Array.set(Object array, int index, int value)
//獲取數組的值,array為數組對象,index為數組的下標
Array.get(Object array, int index)
例子,用反射創建?int[] array = new int[]{1, 2}
Object array = Array.newInstance(int.class, 2);Array.setInt(array , 0, 1);
Array.setInt(array , 1, 2);
注意:反射支持對數據自動加寬,但不允許數據narrowing(變窄?真難翻譯)。意思是對于上述set方法,你可以在int類型數組中 set short類型數據,但不可以set long類型數據,否則會報IllegalArgumentException。
多維數組
Java反射沒有提供能夠直接訪問多維數組元素的API,但你可以把多維數組當成數組的數組處理。
Object matrix = Array.newInstance(int.class, 2, 2);Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
或者
Object matrix = Array.newInstance(int.class, 2);Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);
枚舉
枚舉隱式繼承自java.lang.Enum,Enum繼承自Object,所以枚舉本質也是一個類,也可以有成員變量,構造方法,方法等;對于普通類所能使用的反射方法,枚舉都能使用;另外java反射額外提供了幾個方法為枚舉服務。
| Class.isEnum() | 此類是否為枚舉類型 |
| Class.getEnumConstants() | 返回按照聲明順序索引由枚舉類型定義的枚舉常量的列表 |
| java.lang.reflect.Field.isEnumConstant() | 此字段是否表示枚舉類型的元素 |
10. 常見異常
NoSuchMethodException
造成這種異常的可能有:
方法本身不存在
傳入的參數類型不匹配(也可能是泛型擦除導致)
傳入的參數個數不匹配
反射調用泛型方法時,由于運行前編譯器已經把泛型擦除,參數類型會被擦除為上邊界(默認 Object)。
此時再試圖傳入特定類型的參數會導致NoSuchMethodException異常。
解決的方式是使用 Object 作為參數類型即可。
IllegalAccessException
當你訪問 private 的方法或者 private 的類中的方法,會拋出IllegalAccessException異常。
也有可能是試圖操作 final 修飾的 Field 導致。
解決方法就是給該 method 設置?setAccessible(true)。
llegalArgumentException
如果一個方法沒有參數,但是我們反射時傳入參數,就會導致?llegalArgumentException。
InvocationTargetException
被調用的方法本身所拋出的異常在反射中都會以?InvocationTargetException拋出。
換句話說,反射調用過程中如果異常?InvocationTargetException拋出,說明反射調用本身是成功的,因為這個異常是目標方法本身所拋出的異常。
通過調用?InvocationTargetException?對象的?getCause()?方法,會得到 Throwable 對象,原始的異常就包含在里面。
ClassNotFoundException
Class.forName()傳入的包名有誤,或 Class 本身不存在。將會引發此異常。
NoSuchFieldException
Field 名稱不正確,或對?getField()?和?getDeclaredField()?的使用不正確將會引發此異常。
InstantiationException
實例化出錯,可能是?Class.newInstance()?或者?Constructor.newInstance()?出現了異常。
7簡單使用反射
我們了解的反射的基本情況,能夠獲取 Class 對象,又能從 Class 中獲取相應的信息,記下來記錄如何使用反射。
1. 創建類對象
通過反射創建類對象主要有兩種方式:通過 Class 對象的?newInstance()?方法,或通過 Constructor 對象的?newInstance()?方法。前面提到過,?newInstanse()?方法就是調用無參數的構造方法。
第一種:Class 對象的?newInstance()?方法。
Class clz = Apple.class;Apple apple = (Apple)clz.newInstance();
第二種:Constructor 對象的?newInstance()?方法。
Class clz = Apple.class;Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();
通過 Constructor 對象創建類對象可以選擇特定構造方法,而通過 Class 對象則只能使用默認的無參數構造方法。下面的代碼就調用了一個有參數的構造方法進行了類對象的初始化。
Class clz = Apple.class;Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("紅富士", 15);
2. 獲取屬性
下面例子展示了使用?getDeclaredFields()?獲取全部屬性:
Class clz = Apple.class;Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
3. 調用方法
package org.su18;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Reflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// Class> testClass = TestReflection.class;
Class> testClass = Class.forName("org.su18.TestReflection");
Method funTwoReflect = testClass.getDeclaredMethod("funTwo", String.class, String.class, String.class);
funTwoReflect.setAccessible(true);
funTwoReflect.invoke(null, "Kiki", "Pants", "Dance");
System.out.println("\n");
Method funOneReflect = testClass.getDeclaredMethod("funOne");
funOneReflect.invoke(null);
System.out.println("\n");
Method funThreeReflect = testClass.getDeclaredMethod("funThree", Integer[].class);
funThreeReflect.setAccessible(true);
Integer[] nums = {1,2,3,4,5,6,123,141};
int result = (int) funThreeReflect.invoke(new TestReflection(), (Object) nums);
System.out.println(result);
}
}
class TestReflection {
static void funOne() {
System.out.println("Let's dance");
}
private static void funTwo(String name, String clothes, String action) {
System.out.printf("The First Man Name Is %s .\n", name);
System.out.printf("He Wears Such Little %s .\n", clothes);
System.out.print("His Brother Was A Champion .\n");
System.out.printf("But %s Loves To %s .\n", name, action);
}
private int funThree(Integer... numbers) {
int sum = 0;
for (Integer number : numbers) {
sum += number;
}
return sum;
}
}
上例演示了在各種不同情況下調用的方式的不同結果如下:
4. 調用內部類
假設com.reflect.Outer類,有一個內部類 inner 和靜態內部類 StaticInner 。那么靜態內部類的構造函數為Outer$StaticInner();?而普通內部類的構造函數為Outer$Inner(Outer outer),多了一個 final 的 Outer 類型屬性,即Outer$Inner.this$0,用于存儲外部類的屬性值,也就是說非static內部類保持了外部類的引用。
直接實例化內部類方法如下:
// 靜態內部類Outer.StaticInner sInner = new Outer.StaticInner();
// 非靜態內部類
Outer.Inner inner = new Outer().new Inner();
內部類的類名使用采用 $ 符號,來連接外部類與內部類,格式為outer$Inner。
String className = "com.reflect.Outer$Inner";Class.forName(className);
除了格式了差異,關于內部類的屬性和方法操作基本相似,下面以調用該靜態類的靜態方法為例:
public static Object invokeMethod(String methodName, Class[] argsType, Object... args) {Class clazz = Class.forName("com.reflect.Outer$StaticInner");
Method method = clazz.getDeclaredMethod(methodName, argsType);
method.setAccessible(true);
return method.invoke(null, args);
8
反射的作用
有了如此強大的反射特性,我們可以進行:
為所欲為:理論上可以訪問任意類任意方法任意成員變量,這一步能做的就很多,直接調用某些類的方法、在運行中獲取某些類的某些成員變量、通過獲取注解對web框架獲取到類和地址的映射關系等等。
框架實現:大部分的框架基本都是基于反射實現的。
防護繞過:針對 RASP 技術防護的站,可以反射調用底層方法進行封裝,來繞過一些攔截位置。
還有諸多作用等著大家發掘吧~
9反射的缺點
性能開銷
反射涉及類型動態解析,所以 JVM 無法對這些代碼進行優化。因此,反射操作的效率要比那些非反射操作低得多。我們應該避免在經常被執行的代碼或對性能要求很高的程序中使用反射。安全限制
使用反射技術要求程序必須在一個沒有安全限制的環境中運行。如果一個程序必須在有安全限制的環境中運行,如 Applet,那么這就是個問題了。內部曝光
由于反射允許代碼執行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會導致意料之外的副作用--代碼有功能上的錯誤,降低可移植性。反射代碼破壞了抽象性,因此當平臺發生改變的時候,代碼的行為就有可能也隨著變化。
雖然大家都在說反射性能低,是因為無法通過 JIT 進行優化,但現在的 JDK 已經非常強悍了,并不至于因為性能開銷大而無法使用強大的反射功能。
凌天實驗室凌天實驗室,是安百科技旗下針對應用安全領域進行攻防研究的專業技術團隊,其核心成員來自原烏云創始團隊及社區知名白帽子,團隊專業性強、技術層次高且富有實戰經驗。實驗室成立于2016年,發展至今團隊成員已達35人,在應用安全領域深耕不輟,向網絡安全行業頂尖水平攻防技術團隊的方向夯實邁進。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的java swing jbutton_Java 反射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中l什么是ock接口
- 下一篇: Python中有哪些常用的查找数据结构及