1. 簡介
JAVA反射機制是在執行狀態中。
對於任意一個類,都能夠知道這個類的所有屬性和方法。
對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。
這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。
2. Class
Class類其實也是一個Java類,存在於JDK的java.lang包中。
java在執行時的類資訊就是通過Class物件表示的。它包含了類的所有資訊。
所有的類都是在對其第一次使用時,動態載入到JVM中的(懶載入)。當程式建立第一個對類的靜態成員的引用時,就會載入這個類。使用new建立類物件的時候也會被當作對類的靜態成員的引用。因此java程式程式在它開始執行之前並非被完全載入,其各個類都是在必需時才載入的。
所以,當我們寫的.java檔案被載入到JVM的後,會在方法區生成該類的Class例項。此例項包含了該類的所有資訊。
2.1 如何獲取Class例項
- 型別.class
- 類例項.getClass()
- Class.forName(類的許可權定類名)
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class<Test> testClass = Test.class;
Test test = new Test();
Class<? extends Test> testClass1 = test.getClass();
Class<Test> testClass2 = (Class<Test>) Class.forName("com.ldx.test.Test");
}
}
2.2 使用Class例項
建立一個User
類
package com.ldx.test;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class User {
private String name;
private Integer age;
public boolean isAdmin(String name) {
if("admin".equals(name)) {
return true;
}
return false;
}
}
建立一個測試類Test
package com.ldx.test;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
@Slf4j
public class Test {
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
Field[] declaredFields = userClass.getDeclaredFields();
log.info("User類的名稱:{}", userClass.getSimpleName());
log.info("User類的許可權定類名:{}", userClass.getName());
log.info("User類的欄位有:{}", Arrays.toString(declaredFields));
log.info("User類的方法有:{}", Arrays.toString(userClass.getDeclaredMethods()));
Method isAdmin = userClass.getMethod("isAdmin", String.class);
log.info("User::isAdmin方法返回值為:" + isAdmin.invoke(userClass.newInstance(), "admin"));
}
}
輸出內容如下:
com.ldx.test.Test - User類的名稱:User
com.ldx.test.Test - User類的許可權定類名:com.ldx.test.User
com.ldx.test.Test - User類的欄位有:[private java.lang.String com.ldx.test.User.name, private java.lang.Integer com.ldx.test.User.age]
com.ldx.test.Test - User類的方法有:[public boolean com.ldx.test.User.isAdmin(java.lang.String), public java.lang.Integer com.ldx.test.User.getAge(), public void com.ldx.test.User.setAge(java.lang.Integer), public java.lang.String com.ldx.test.User.toString(), public java.lang.String com.ldx.test.User.getName(), public void com.ldx.test.User.setName(java.lang.String)]
com.ldx.test.Test - User::isAdmin方法返回值為:true
2.3 常用方法摘要
返回值 | 方法說明 |
---|---|
<U> Class<? extends U> |
asSubclass(Class<U> clazz) 強制轉換該 Class 物件,以表示指定的 class 物件所表示的類的一個子類。 |
static Class<?> |
forName(String className) 返回與帶有給定字串名的類或介面相關聯的 Class 物件。 |
<A extends Annotation>A |
getAnnotation(Class<A> annotationClass) 如果存在該元素的指定型別的註解,則返回這些註解,否則返回 null。 |
Annotation[] |
getAnnotations() 返回此元素上存在的所有註解。 |
ClassLoader |
getClassLoader() 返回該類的類載入器。 |
Class<?> |
getComponentType() 返回表示陣列元件型別的 Class 。 |
Annotation[] |
getDeclaredAnnotations() 返回直接存在於此元素上的所有註解。 |
Class<?> |
getDeclaringClass() 如果此 Class 物件所表示的類或介面是另一個類的成員,則返回的 Class 物件表示該物件的宣告類。 |
T[] |
getEnumConstants() 如果此 Class 物件不表示列舉型別,則返回列舉類的元素或 null。 |
Type[] |
getGenericInterfaces() 返回表示某些介面的 Type ,這些介面由此物件所表示的類或介面直接實現。 |
Type |
getGenericSuperclass() 返回表示此 Class 所表示的實體(類、介面、基本型別或 void)的直接超類的 Type 。 |
Class<?>[] |
getInterfaces() 確定此物件所表示的類或介面實現的介面。 |
int |
getModifiers() 返回此類或介面以整數編碼的 Java 語言修飾符。 |
String |
getName() 以 String 的形式返回此 Class 物件所表示的實體(類、介面、陣列類、基本型別或 void)名稱。 |
String |
getCanonicalName() 返回 Java Language Specification 中所定義的底層類的規範化名稱。主要用於輸出(toString)或log列印,大多數情況下和 getName() 一樣,但是在內部類、陣列等型別的表示形式就不同了。不能用getCanonicalName() 去載入類物件,必須用getName() , |
Package |
getPackage() 獲取此類的包。 |
InputStream |
getResourceAsStream(String name) 查詢具有給定名稱的資源。 |
String |
getSimpleName() 返回原始碼中給出的底層類的簡稱。 |
Class<? super T> |
getSuperclass() 返回表示此 Class 所表示的實體(類、介面、基本型別或 void)的超類的 Class 。 |
boolean |
isAnnotation() 如果此 Class 物件表示一個註解型別則返回 true。 |
boolean |
isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定型別的註解存在於此元素上,則返回 true,否則返回 false。 |
boolean |
isAnonymousClass() 當且僅當底層類是匿名類時返回 true 。 |
boolean |
isArray() 判定此 Class 物件是否表示一個陣列類。 |
boolean |
isAssignableFrom(Class<?> cls) 判定此 Class 物件所表示的類或介面與指定的 Class 引數所表示的類或介面是否相同,或是否是其超類或超介面。 |
boolean |
isEnum() 當且僅當該類宣告為原始碼中的列舉時返回 true。 |
boolean |
isInstance(Object obj) 如果obj是這個類的一個例項此方法返回true。 |
boolean |
isInterface() 判定指定的 Class 物件是否表示一個介面型別。 |
boolean |
isLocalClass() 當且僅當這個類是區域性類此方法返回true。 |
boolean |
isMemberClass() 當且僅當底層類是成員類時返回 true 。 |
boolean |
isPrimitive() 判定指定的 Class 物件是否表示一個基本型別。 |
T |
newInstance() 建立此 Class 物件所表示的類的一個新例項。 |
3. new Instance
想通過反射建立物件大概有以下幾種方式:
- 通過
Class.newInstance()
直接建立物件。 - 通過Class例項獲取到
Constructor
(構造器),通過構造器建立物件。
獲取構造方法的途徑有以下幾種:
返回值 | 方法說明 |
---|---|
Constructor |
getConstructor(Class<?>... parameterTypes) 返回一個 Constructor 物件,它反映此 Class 物件所表示的類的public構造方法。 |
Constructor<?>[] |
getConstructors() 返回所有 Constructor 物件,它反映此 Class 物件所表示的類的public構造方法。 |
Constructor |
getDeclaredConstructor(Class<?>... parameterTypes) 返回一個 Constructor 物件,該物件反映此 Class 物件所表示的類的public/private構造方法。 |
Constructor<?>[] |
getDeclaredConstructor() 返回所有 Constructor 物件,該物件反映此 Class 物件所表示的類的public/private構造方法。 |
示例程式碼如下:
@Getter
@Setter
@ToString
public class User {
private String name;
private Integer age;
public User() {
this.name = "張三";
this.age = 24;
}
public boolean isAdmin(String name) {
if("admin".equals(name)) {
return true;
}
return false;
}
}
@Slf4j
public class Test {
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
User user = userClass.newInstance();
Constructor<User> constructor = userClass.getConstructor();
User user1 = constructor.newInstance();
Constructor<User> declaredConstructor = userClass.getDeclaredConstructor();
// 如果構造器為private,則可以設定訪問許可權為true,即可newInstance
declaredConstructor.setAccessible(true);
User user2 = declaredConstructor.newInstance();
log.info(user.toString());
log.info(user1.toString());
log.info(user2.toString());
}
}
輸出內容如下:
15:59:01.653 [main] INFO com.ldx.test.Test - User(name=張三, age=24)
15:59:01.663 [main] INFO com.ldx.test.Test - User(name=張三, age=24)
15:59:01.663 [main] INFO com.ldx.test.Test - User(name=張三, age=24)
4. Method
返回值 | 方法說明 |
---|---|
Method |
getMethod(String name, Class<?>... parameterTypes) 返回一個 Method 物件,它反映此 Class 物件所表示的類的public方法。(可以獲取父類的方法) |
Method[] |
getMethods() 返回所有 Method 物件,它反映此 Class 物件所表示的類的public方法。(可以獲取父類的方法) |
Method |
getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一個 Method 物件,該物件反映此 Class 物件所表示的類的public/private方法。(只獲取當前類的方法) |
Method[] |
getDeclaredMethods() 返回所有 Method 物件,它反映此 Class 物件所表示的類的public/private方法。(只獲取當前類的方法) |
示例程式碼如下:
@Slf4j
public class Test {
public static void main(String[] args) throws Exception {
Class<Man> manClass = Man.class;
Man man = manClass.newInstance();
Method getHairColor = manClass.getMethod("getHairColor");
Method getHairColor1 = manClass.getDeclaredMethod("getHairColor");
// 如果方法為private,則可以設定訪問許可權為true,即可newInstance
getHairColor1.setAccessible(true);
log.info(getHairColor.invoke(man).toString());
log.info(getHairColor1.invoke(man).toString());
// 可以呼叫到父類方法
Method isAdmin = manClass.getMethod("isAdmin", String.class);
log.info(isAdmin.invoke(man, "admin").toString());
// 獲取不到父類方法
Method isAdmin1 = manClass.getDeclaredMethod("isAdmin", String.class);
log.info(isAdmin1.invoke(man, "admin").toString());
}
}
class Man extends User {
public String getHairColor() {
return "一頭黑色秀髮";
}
}
輸出內容如下:
16:30:49.809 [main] INFO com.ldx.test.Test - 一頭黑色秀髮
16:30:49.818 [main] INFO com.ldx.test.Test - 一頭黑色秀髮
16:30:49.818 [main] INFO com.ldx.test.Test - true
Exception in thread "main" java.lang.NoSuchMethodException: com.ldx.test.Man.isAdmin(java.lang.String)
at java.lang.Class.getDeclaredMethod(Class.java:2130)
at com.ldx.test.Test.main(Test.java:31)
Method類除了有上面的invoke
方法,常用方法還有如下:
返回值 | 方法說明 |
---|---|
<T extends Annotation>T |
getAnnotation(Class<T> annotationClass) 如果存在該元素的指定型別的註解,則返回這些註釋,否則返回 null。 |
Annotation[] |
getDeclaredAnnotations() 返回直接存在於此元素上的所有註解。 |
Class<?> |
getDeclaringClass() 返回表示宣告由此 Method 物件表示的方法的類或介面的 Class 物件。 |
Type[] |
getGenericParameterTypes() 按照宣告順序返回 Type 物件的陣列,這些物件描述了此 Method 物件所表示的方法的形參型別的。 |
Type |
getGenericReturnType() 返回表示由此 Method 物件所表示方法的正式返回型別的 Type 物件。 |
int |
getModifiers() 以整數形式返回此 Method 物件所表示方法的 Java 語言修飾符。 |
String |
getName() 以 String 形式返回此 Method 物件表示的方法名稱。 |
Annotation[][] |
getParameterAnnotations() 返回表示按照宣告順序對此 Method 物件所表示方法的形參進行註解的那個陣列的陣列。 |
Class<?>[] |
getParameterTypes() 按照宣告順序返回 Class 物件的陣列,這些物件描述了此 Method 物件所表示的方法的形參型別。 |
Class<?> |
getReturnType() 返回一個 Class 物件,該物件描述了此 Method 物件所表示的方法的正式返回型別。 |
Object |
invoke(Object obj, Object... args) 對帶有指定引數的指定物件呼叫由此 Method 物件表示的底層方法。 |
boolean |
isVarArgs() 如果將此方法宣告為帶有可變數量的引數,則返回 true ;否則,返回 false 。 |
String |
toGenericString() 返回描述此 Method 的字串,包括型別引數。 |
void |
setAccessible(boolean flag) 將此物件的 accessible 標誌設定為指示的布林值,即設定其可訪問性。 |
其中:getReturnType/getGenericReturnType
都是獲取Method物件表示的方法的返回型別,只不過前者返回的Class型別後者返回的Type,Type就是一個介面而已,在Java8中新增一個預設的方法實現,返回的就引數型別資訊。getParameterTypes/getGenericParameterTypes
亦是如此。
其返回結果和Class.getTypeName()
放回結果相同,就是輸出返回值的類的name
(帶包名)。
public interface Type {
default String getTypeName() {
return toString();
}
}
5. Field
返回值 | 方法說明 |
---|---|
Field |
getField(String name) 返回一個 Field 物件,它反映此 Class 物件所表示的類的public屬性。(可以獲取父類的方法) |
Field[] |
getFields() 返回所有 Field 屬性,它反映此 Class 物件所表示的類的public屬性。(可以獲取父類的方法) |
Field |
getDeclaredField(String name) 返回一個 Field 物件,該物件反映此 Class 物件所表示的類的public/private屬性。(只獲取當前類的方法) |
Field[] |
getDeclaredFields() 返回所有 Field 屬性,該物件反映此 Class 物件所表示的類的public/private屬性。(只獲取當前類的方法) |
示例程式碼如下:
@Slf4j
public class Test {
public static void main(String[] args) throws Exception {
Class<Man> manClass = Man.class;
Man man = manClass.newInstance();
Field hair = manClass.getDeclaredField("hair");
hair.setAccessible(true);
hair.set(man, "濃密的黑髮");
log.info(hair.get(man).toString());
log.info(man.getHair());
Class<?> type = hair.getType();
Class<?> declaringClass = hair.getDeclaringClass();
log.info(type.getCanonicalName());
log.info(declaringClass.getCanonicalName());
}
}
@Getter
class Man extends User {
private String hair;
}
輸出內容如下:
17:13:17.903 [main] INFO com.ldx.test.Test - 濃密的黑髮
17:13:17.909 [main] INFO com.ldx.test.Test - 濃密的黑髮
17:13:17.909 [main] INFO com.ldx.test.Test - java.lang.String
17:13:17.909 [main] INFO com.ldx.test.Test - com.ldx.test.Man
Field類除了有上面寫到的方法,常用方法還有如下:
返回值 | 方法說明 |
---|---|
Object |
get(Object obj) 返回指定物件上此 Field 表示的欄位的值。 |
<T extends Annotation>T |
getAnnotation(Class<T> annotationClass) 如果存在該元素的指定型別的註解,則返回這些註釋,否則返回 null。 |
boolean |
getBoolean(Object obj) 獲取一個靜態或例項 boolean 欄位的值。 |
byte |
getByte(Object obj) 獲取一個靜態或例項 byte 欄位的值。 |
char |
getChar(Object obj) 獲取 char 型別或另一個通過擴充套件轉換可以轉換為 char 型別的基本型別的靜態或例項欄位的值。 |
Annotation[] |
getDeclaredAnnotations() 返回直接存在於此元素上的所有註解。 |
Class<?> |
getDeclaringClass() 返回表示類或介面的 Class 物件,該類或介面宣告由此 Field 物件表示的欄位。 |
double |
getDouble(Object obj) 獲取 double 型別或另一個通過擴充套件轉換可以轉換為 double 型別的基本型別的靜態或例項欄位的值。 |
float |
getFloat(Object obj) 獲取 float 型別或另一個通過擴充套件轉換可以轉換為 float 型別的基本型別的靜態或例項欄位的值。 |
Type |
getGenericType() 返回一個 Type 物件,它表示此 Field 物件所表示欄位的宣告型別。 |
int |
getInt(Object obj) 獲取 int 型別或另一個通過擴充套件轉換可以轉換為 int 型別的基本型別的靜態或例項欄位的值。 |
long |
getLong(Object obj) 獲取 long 型別或另一個通過擴充套件轉換可以轉換為 long 型別的基本型別的靜態或例項欄位的值。 |
int |
getModifiers() 以整數形式返回由此 Field 物件表示的欄位的 Java 語言修飾符。 |
String |
getName() 返回此 Field 物件表示的欄位的名稱。 |
short |
getShort(Object obj) 獲取 short 型別或另一個通過擴充套件轉換可以轉換為 short 型別的基本型別的靜態或例項欄位的值。 |
Class<?> |
getType() 返回一個 Class 物件,它標識了此 Field 物件所表示欄位的宣告型別。 |
boolean |
isEnumConstant() 如果此欄位表示列舉型別的元素,則返回 true ;否則返回 false 。 |
void |
set(Object obj, Object value) 將指定物件變數上此 Field 物件表示的欄位設定為指定的新值。 |
void |
setBoolean(Object obj, boolean z) 將欄位的值設定為指定物件上的一個 boolean 值。 |
void |
setByte(Object obj, byte b) 將欄位的值設定為指定物件上的一個 byte 值。 |
void |
setChar(Object obj, char c) 將欄位的值設定為指定物件上的一個 char 值。 |
void |
setDouble(Object obj, double d) 將欄位的值設定為指定物件上的一個 double 值。 |
void |
setFloat(Object obj, float f) 將欄位的值設定為指定物件上的一個 float 值。 |
void |
setInt(Object obj, int i) 將欄位的值設定為指定物件上的一個 int 值。 |
void |
setLong(Object obj, long l) 將欄位的值設定為指定物件上的一個 long 值。 |
void |
setShort(Object obj, short s) 將欄位的值設定為指定物件上的一個 short 值。 |
String |
toGenericString() 返回一個描述此 Field (包括其一般型別)的字串。 |
void |
setAccessible(boolean flag) 將此物件的 accessible 標誌設定為指示的布林值,即設定其可訪問性。 |