
微信搜索:碼農StayUp
主頁地址:https://gozhuyinglong.github.io
原始碼分享:https://github.com/gozhuyinglong/blog-demos
1. 前言
在OOP的世界里,萬物皆物件,也就是說,我們可以將任何東西抽象成一個物件,
比如人,可以抽象成一個Person類,通過new Person()來實體化一個物件;再比如鴨子,可以抽象成一個Duck類,也可以對其進行實體化……那么這一個個類本身是不是也可以抽象成一個類呢?Java提供了一個特殊的類Class,用來描述類的內部資訊,是反射的核心類,
下圖是本篇講述內容:

2. Java反射機制概述
Java反射(Reflection)允許應用程式在運行時借助于反射API,來獲取所有類或介面的內部資訊,并且能直接操作任意物件的內部屬性及方法,反射機制的核心類為java.lang.Class,
- 類加載完后,會在堆記憶體的方法區中產生一個
Class型別的物件, Class類沒有公開的建構式,是由類加載器的defineClass方法構造而成,所以Class物件不是“new”出來的,而是通過方法來獲取的,- 這個
Class物件具有類的完整結構資訊,并且一個類只有一個Class物件,
3. 獲取Class物件
獲取Class物件有以下四種方式:
- 通過類物件獲取;
- 通過類直接呼叫class獲取;
- 通過Class.forName獲取;
- 通過類加載器獲取,
下面使用代碼展示獲取 Person 類的Class物件的四種方式:
@Test
public void testClassFor() {
// 1.通過類實體獲取
Person person = new Person();
Class<? extends Person> clazz1 = person.getClass();
System.out.println("01 - " + clazz1);
// 2.通過類直接呼叫class獲取
Class<Person> clazz2 = Person.class;
System.out.println("02 - " + clazz2);
// 3.通過Class.forName獲取
Class<?> clazz3 = null;
try {
clazz3 = Class.forName("io.github.gozhuyinglong.reflection.Person");
} catch (ClassNotFoundException e) {
// 當找不到指定類時,會拋出此例外
e.printStackTrace();
}
System.out.println("03 - " + clazz3);
// 4.通過類加載器獲取
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?> clazz4 = null;
try {
clazz4 = classLoader.loadClass("io.github.gozhuyinglong.reflection.Person");
} catch (ClassNotFoundException e) {
// 當找不到指定類時,會拋出此例外
e.printStackTrace();
}
System.out.println("04 - " + clazz4);
// hashCode相等,說明這四種方式獲取的是同一個實體
System.out.println("05 - " + clazz1.hashCode());
System.out.println("06 - " + clazz2.hashCode());
System.out.println("07 - " + clazz3.hashCode());
System.out.println("08 - " + clazz4.hashCode());
}
輸出結果:
01 - class io.github.gozhuyinglong.reflection.Person
02 - class io.github.gozhuyinglong.reflection.Person
03 - class io.github.gozhuyinglong.reflection.Person
04 - class io.github.gozhuyinglong.reflection.Person
05 - 721748895
06 - 721748895
07 - 721748895
08 - 721748895
通過上面的輸出結果可以看出,這四個Class物件的hashCode相同,說明使用這四種方式獲取的是同一個物件,
4. 一些特殊的類和介面的Class物件
在原始碼注釋中提到一些特殊的類和介面:
- 列舉是一種類,
- 注解是一種介面,
- 陣列也屬于一個反映為
Class物件的類,具有相同元素型別和維數的陣列,也具有相同的Class物件(也就是說,元素型別不同,或陣列維數不同,其Class物件也不同), - 原始Java型別(
boolean,byte,char,short,int,long,float,double)和關鍵字void也表示為Class物件,
下面通過代碼來驗證:
@Test
public void testClassOther() {
// 列舉是一種類
Class<PersonEnum> clazz1 = PersonEnum.class;
System.out.println("01 - " + clazz1);
// 注解是一種介面
Class<PersonAnnotation> clazz2 = PersonAnnotation.class;
System.out.println("02 - " + clazz2);
// 陣列也屬于一個反應 Class 實體的類
Person[] personArray3 = new Person[1];
Class<? extends Person[]> clazz3 = personArray3.getClass();
System.out.println("03 - " + clazz3);
// 具有相同元素型別和維數的陣列,也具有相同的 Class 實體
Person[] personArray4 = new Person[4];
Class<?> clazz4 = personArray4.getClass();
Person[][] personArray5 = new Person[1][];
Class<?> clazz5 = personArray5.getClass();
// 兩個一維陣列的 hashCode 相等,說明是同一實體
System.out.println("04 - " + clazz3.hashCode());
System.out.println("05 - " + clazz4.hashCode());
// 一維陣列與二維陣列的 hashCode 不相等,說明不是同一實體
System.out.println("06 - " + clazz5.hashCode());
// 原始 Java 型別和關鍵字 void 也表示為 Class 實體
Class<Integer> clazz6 = int.class;
System.out.println("07 - " + clazz6);
Class<Double> clazz7 = double.class;
System.out.println("08 - " + clazz7);
Class<Void> clazz8 = void.class;
System.out.println("09 - " + clazz8);
}
輸出結果:
01 - class io.github.gozhuyinglong.reflection.PersonEnum
02 - interface io.github.gozhuyinglong.reflection.PersonAnnotation
03 - class [Lio.github.gozhuyinglong.reflection.Person;
04 - 721748895
05 - 721748895
06 - 1642534850
07 - int
08 - double
09 - void
通過輸出結果可以看出,確如原始碼中描述那樣,
5. Java反射API
Java提供了一套反射API,該API由Class類與java.lang.reflect類別庫組成,該類別庫包含了Field、Method、Constructor等類,這些型別的物件是由JVM在運行時創建的,用以表示未知類里對應的成員,
反射允許以編程的方式訪問已加載類的欄位、方法和建構式資訊,并在安全限制內利用反射對其進行操作,
下面將介紹一些常用的類:
5.1 Class(類)
java.lang.Class類用來描述類的內部資訊,Class的實體可以獲取類的包、注解、修飾符、名稱、超類、介面等,
@Test
public void testClass() throws Exception {
Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");
// 獲取該類所在包路徑
Package aPackage = clazz.getPackage();
System.out.println("01 - " + aPackage);
// 獲取該類上所有注解
Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
for (Annotation temp : declaredAnnotations) {
System.out.println("02 - " + temp);
}
// 獲取類上的修飾符
int modifiers = clazz.getModifiers();
String modifier = Modifier.toString(modifiers);
System.out.println("03 - " + modifier);
// 獲取類名稱
String name = clazz.getName();
System.out.println("04 - " + name);
// 獲取簡單類名
String simpleName = clazz.getSimpleName();
System.out.println("05 - " + simpleName);
// 獲取直屬超類
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println("06 - " + genericSuperclass);
// 獲取直屬實作的介面
Type[] genericInterfaces = clazz.getGenericInterfaces();
for (Type temp : genericInterfaces) {
System.out.println("07 - " + temp);
}
}
輸出結果:
01 - package io.github.gozhuyinglong.reflection
02 - @io.github.gozhuyinglong.reflection.PersonAnnotation()
03 - public final
04 - io.github.gozhuyinglong.reflection.Person
05 - Person
06 - class io.github.gozhuyinglong.reflection.PersonParent
07 - interface io.github.gozhuyinglong.reflection.PersonInterface
5.2 Constructor(建構式)
java.lang.reflect.Constructor提供了類的建構式資訊,可以獲取建構式上的注解資訊、引數型別等,
@Test
public void testConstructor() throws Exception {
Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");
// 獲取一個宣告為 public 建構式實體
Constructor<?> constructor1 = clazz.getConstructor(String.class, int.class, PersonEnum.class);
System.out.println("01 - " + constructor1);
// 獲取所有宣告為 public 建構式實體
Constructor<?>[] constructorArray1 = clazz.getConstructors();
for (Constructor<?> constructor : constructorArray1) {
System.out.println("02 - " + constructor);
}
// 獲取一個宣告的建構式實體
Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class);
System.out.println("03 - " + constructor2);
// 獲取所有宣告的建構式實體
Constructor<?>[] constructorArray2 = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructorArray2) {
System.out.println("04 - " + constructor);
}
// 根據建構式創建一個實體
Object o1 = constructor1.newInstance("楊過", 25, PersonEnum.MAN);
System.out.println("05 - " + o1);
// 將建構式的可訪問標志設為 true 后,可以通過私有建構式創建實體
constructor2.setAccessible(true);
Object o2 = constructor2.newInstance("小龍女");
System.out.println("06 - " + o2);
// 獲取該建構式上的所有注解
Annotation[] annotations = constructor1.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
System.out.println("07 - " + annotation);
}
// 獲取該建構式上的所有引數型別
Type[] genericParameterTypes = constructor1.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("08 - " + genericParameterType);
}
}
輸出結果:
01 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int,io.github.gozhuyinglong.reflection.PersonEnum)
02 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int,io.github.gozhuyinglong.reflection.PersonEnum)
02 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int)
02 - public io.github.gozhuyinglong.reflection.Person()
03 - private io.github.gozhuyinglong.reflection.Person(java.lang.String)
04 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int,io.github.gozhuyinglong.reflection.PersonEnum)
04 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int)
04 - private io.github.gozhuyinglong.reflection.Person(java.lang.String)
04 - public io.github.gozhuyinglong.reflection.Person()
05 - Person{name='楊過', age=25, sex='MAN'}
06 - Person{name='小龍女', age=0, sex='null'}
07 - @io.github.gozhuyinglong.reflection.PersonAnnotation()
08 - class java.lang.String
08 - int
08 - class io.github.gozhuyinglong.reflection.PersonEnum
5.3 Field(屬性)
java.lang.reflect.Field提供了類的屬性資訊,可以獲取屬性上的注解、修飾符、屬性型別、屬性名等,
@Test
public void testField() throws Exception {
Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");
// 獲取一個該類或父類中宣告為 public 的屬性
Field field1 = clazz.getField("hobby");
System.out.println("01 - " + field1);
// 獲取該類及父類中所有宣告為 public 的屬性
Field[] fieldArray1 = clazz.getFields();
for (Field field : fieldArray1) {
System.out.println("02 - " + field);
}
// 獲取一個該類中宣告的屬性
Field field2 = clazz.getDeclaredField("name");
System.out.println("03 - " + field2);
// 獲取該類中所有宣告的屬性
Field[] fieldArray2 = clazz.getDeclaredFields();
for (Field field : fieldArray2) {
System.out.println("04 - " + field);
}
// 獲取該屬性上的所有注解
Annotation[] declaredAnnotations = field2.getDeclaredAnnotations();
for (Annotation declaredAnnotation : declaredAnnotations) {
System.out.println("05 - " + declaredAnnotation);
}
// 獲取修飾符
String modifier = Modifier.toString(field2.getModifiers());
System.out.println("06 - " + modifier);
// 獲取屬性型別,回傳類物件
Class<?> type = field2.getType();
System.out.println("07 - " + type);
// 獲取屬性型別,回傳Type物件
Type genericType = field2.getGenericType();
System.out.println("08 - " + genericType);
// 獲取屬性名稱
String name = field2.getName();
System.out.println("09 - " + name);
}
輸出結果:
01 - public java.lang.String io.github.gozhuyinglong.reflection.PersonParent.hobby
02 - public int io.github.gozhuyinglong.reflection.Person.height
02 - public java.lang.String io.github.gozhuyinglong.reflection.PersonParent.hobby
03 - private java.lang.String io.github.gozhuyinglong.reflection.Person.name
04 - private java.lang.String io.github.gozhuyinglong.reflection.Person.name
04 - private int io.github.gozhuyinglong.reflection.Person.age
04 - public int io.github.gozhuyinglong.reflection.Person.height
05 - @io.github.gozhuyinglong.reflection.PersonAnnotation()
06 - private
07 - class java.lang.String
08 - class java.lang.String
09 - name
5.4 Method(方法)
java.lang.reflect.Method提供了類的方法資訊,可以獲取方法上的注解、修飾符、回傳值型別、方法名稱、所有引數,
@Test
public void testMethod() throws Exception {
Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");
// 獲取一個該類及父類中宣告為 public 的方法,需要指定方法的入參型別
Method method = clazz.getMethod("setName", String.class);
System.out.println("01 - " + method);
// 獲取該類及父類中所有宣告為 public 的方法
Method[] methods = clazz.getMethods();
for (Method temp : methods) {
System.out.println("02 - " + temp);
}
// 獲取一個在該類中宣告的方法
Method declaredMethod = clazz.getDeclaredMethod("display");
System.out.println("03 - " + declaredMethod);
// 獲取所有在該類中宣告的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method temp : declaredMethods) {
System.out.println("04 - " + temp);
}
// 獲取該方法上的所有注解
Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
for (Annotation temp : declaredAnnotations) {
System.out.println("05 - " + temp);
}
// 獲取修飾符
String modifier = Modifier.toString(method.getModifiers());
System.out.println("06 - " + modifier);
// 獲取回傳值型別,回傳類物件
Class<?> returnType = method.getReturnType();
System.out.println("07 - " + returnType);
// 獲取回傳值型別,回傳Type物件
Type genericReturnType = method.getGenericReturnType();
System.out.println("08 - " + genericReturnType);
// 獲取方法名稱
String name = method.getName();
System.out.println("09 - " + name);
// 獲取所有入參
Parameter[] parameters = method.getParameters();
for (Parameter temp : parameters) {
System.out.println("10 - " + temp);
}
}
輸出結果:
01 - public void io.github.gozhuyinglong.reflection.Person.setName(java.lang.String)
02 - public java.lang.String io.github.gozhuyinglong.reflection.Person.toString()
02 - public java.lang.String io.github.gozhuyinglong.reflection.Person.getName()
02 - public void io.github.gozhuyinglong.reflection.Person.setName(java.lang.String)
02 - public int io.github.gozhuyinglong.reflection.Person.getAge()
02 - public void io.github.gozhuyinglong.reflection.Person.setAge(int)
02 - public java.lang.String io.github.gozhuyinglong.reflection.Person.sayHello()
02 - public io.github.gozhuyinglong.reflection.PersonEnum io.github.gozhuyinglong.reflection.PersonParent.getSex()
02 - public void io.github.gozhuyinglong.reflection.PersonParent.setSex(io.github.gozhuyinglong.reflection.PersonEnum)
02 - public final void java.lang.Object.wait() throws java.lang.InterruptedException
02 - public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
02 - public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
02 - public boolean java.lang.Object.equals(java.lang.Object)
02 - public native int java.lang.Object.hashCode()
02 - public final native java.lang.Class java.lang.Object.getClass()
02 - public final native void java.lang.Object.notify()
02 - public final native void java.lang.Object.notifyAll()
03 - private java.lang.String io.github.gozhuyinglong.reflection.Person.display()
04 - public java.lang.String io.github.gozhuyinglong.reflection.Person.toString()
04 - public java.lang.String io.github.gozhuyinglong.reflection.Person.getName()
04 - public void io.github.gozhuyinglong.reflection.Person.setName(java.lang.String)
04 - private java.lang.String io.github.gozhuyinglong.reflection.Person.display()
04 - public int io.github.gozhuyinglong.reflection.Person.getAge()
04 - public void io.github.gozhuyinglong.reflection.Person.setAge(int)
04 - public java.lang.String io.github.gozhuyinglong.reflection.Person.sayHello()
05 - @io.github.gozhuyinglong.reflection.PersonAnnotation()
06 - public
07 - void
08 - void
09 - setName
10 - java.lang.String arg0
5.5 Modifier(修飾符)
java.lang.reflect.Modifier提供了訪問修飾符資訊,通過Class、Field、Method、Constructor等物件都可以獲取修飾符,這個訪問修飾符是一個整數,可以通過Modifier.toString方法來查看修飾符描述,并且該類提供了一些靜態方法和常量來解碼訪問修飾符,
@Test
public void testModifier() throws Exception {
Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");
// 獲取類的修飾符值
int modifiers1 = clazz.getModifiers();
System.out.println("01 - " + modifiers1);
// 獲取屬性的修飾符值
int modifiers2 = clazz.getDeclaredField("name").getModifiers();
System.out.println("02 - " + modifiers2);
// 獲取建構式的修飾符值
int modifiers3 = clazz.getDeclaredConstructor(String.class).getModifiers();
System.out.println("03 - " + modifiers3);
// 獲取方法的修飾符值
int modifiers4 = clazz.getDeclaredMethod("display").getModifiers();
System.out.println("04 - " + modifiers4);
// 判斷修飾符值是否 final 型別
boolean isFinal = Modifier.isFinal(modifiers1);
System.out.println("05 - " + isFinal);
// 判斷修飾符值是否 public 型別
boolean isPublic = Modifier.isPublic(modifiers2);
System.out.println("06 - " + isPublic);
// 根據修飾符值,獲取修飾符標志的字串
String modifier = Modifier.toString(modifiers1);
System.out.println("07 - " + modifier);
System.out.println("08 - " + Modifier.toString(modifiers2));
}
輸出結果:
01 - 17
02 - 2
03 - 2
04 - 2
05 - true
06 - false
07 - public final
08 - private
5.6 Parameter(引數)
java.lang.reflect.Parameter提供了方法的引數資訊,可以獲取方法上的注解、引數名稱、引數型別等,
@Test
public void testParameter() throws Exception {
Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");
// 獲取建構式的引數
Constructor<?> constructor = clazz.getConstructor(String.class, int.class, PersonEnum.class);
Parameter[] parameterArray1 = constructor.getParameters();
for (Parameter temp : parameterArray1) {
System.out.println("01 - " + temp);
}
// 獲取方法的引數
Method method = clazz.getMethod("setName", String.class);
Parameter[] parameterArray2 = method.getParameters();
for (Parameter temp : parameterArray2) {
System.out.println("02 - " + temp);
}
Parameter parameter = parameterArray1[0];
// 獲取引數上的注解
Annotation[] annotationArray = parameter.getAnnotations();
for (Annotation temp : annotationArray) {
System.out.println("02 - " + temp);
}
// 獲取引數名稱
String name = parameter.getName();
System.out.println("03 - " + name);
// 獲取引數型別
Type parameterizedType = parameter.getParameterizedType();
System.out.println("04 - " + parameterizedType);
Class<?> type = parameter.getType();
System.out.println("05 - " + type);
}
輸出結果:
01 - java.lang.String arg0
01 - int arg1
01 - io.github.gozhuyinglong.reflection.PersonEnum arg2
02 - java.lang.String arg0
02 - @io.github.gozhuyinglong.reflection.PersonAnnotation()
03 - arg0
04 - class java.lang.String
05 - class java.lang.String
5.7 AccessibleObject(可訪問標志)
java.lang.reflect.AccessibleObject類是Field、Method和Constructor類的超類,
該類提供了對類、方法、建構式的訪問控制檢查的能力(如:私有方法只允許當前類訪問),
該訪問檢查在設定/獲取屬性、呼叫方法、創建/初始化類的實體時執行,
可以通過setAccessible方法將可訪問標志設為true(默認為false),會關閉訪問檢查,這樣即使是私有的屬性、方法或建構式,也可以訪問,
6. 通過反射動態創建物件并執行方法
可以利用反射來創建物件,并可執行方法,下面看代碼示例:
- 通過
Class類的newInstance創建一個實體,(該方法呼叫無參構造器), - 通過建構式
Constructor類創建一個實體, - 獲取方法,再通過
invoke方法來呼叫,第一個引數為實體,后面引數為方法的Parameter, - 獲取欄位,因為 age 欄位是私有的,所以將其設定為可訪問(不設定會報例外),并通過
set方法來賦值,
@Test
public void testInvoke() throws Exception {
Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");
// 通過Class類的newInstance創建一個實體,(該方法呼叫無參構造器)
Object o1 = clazz.newInstance();
System.out.println("01 - " + o1.toString());
// 通過建構式Constructor類創建一個實體
Constructor<?> constructor = clazz.getConstructor(String.class, int.class, PersonEnum.class);
Object o2 = constructor.newInstance("楊過", 25, PersonEnum.MAN);
System.out.println("02 - " + o2.toString());
// 先獲取方法,再通過 invoke 方法來呼叫,第一個引數為實體,后面引數為方法的Parameter
Method method = clazz.getMethod("setName", String.class);
method.invoke(o1, "小龍女");
System.out.println("03 - " + o1.toString());
// 獲取欄位,因為 age 欄位是私有的,所以將其設定為可訪問(不設定會報例外),并通過 set 方法來賦值
Field field = clazz.getDeclaredField("age");
field.setAccessible(true);
field.set(o1, 28);
System.out.println("04 - " + o1.toString());
}
執行結果:
01 - Person{name='null', age=0, sex='null'}
02 - Person{name='楊過', age=25, sex='MAN'}
03 - Person{name='小龍女', age=0, sex='null'}
04 - Person{name='小龍女', age=28, sex='null'}
7. 反射的缺點
引自官方指南:https://docs.oracle.com/javase/tutorial/reflect/index.html
反射雖是強大的,但不可隨意使用,如果可以在不使用反射的情況下執行操作,則應避免使用它,因為通過反射訪問代碼時,會有以下缺點,
7.1 性能開銷
反射包括了一些動態型別,所以JVM無法對這些代碼進行優化,因此,反射操作的效率要比那些非反射操作低得多,我們應該避免在經常被執行的代碼或對性能要求很高的程式中使用反射,
7.2 安全限制
使用反射技術要求程式必須在一個沒有安全限制的環境中運行,如果一個程式必須在有安全限制的環境中運行,如Applet,那么這就是個問題了,
7.3 內部暴露
由于反射允許代碼執行一些在正常情況下不被允許的操作,比如訪問私有的屬性和方法,所以使用反射可能會導致意料之外的副作用:代碼有功能上的錯誤,降低可移植性,反射代碼破壞了抽象性,因此當平臺發生改變的時候,代碼的行為就有可能也隨著變化,
8. 完整代碼
完整代碼請訪問我的Github,若對你有幫助,歡迎給個?,感謝~~🌹🌹🌹
https://github.com/gozhuyinglong/blog-demos/tree/main/java-source-analysis/src/main/java/io/github/gozhuyinglong/reflection
9. 參考資料
- Java? Platform, Standard Edition 8 API Specification
- The Java? Tutorials

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/260571.html
標籤:java
