目錄
1. 前言
本文會介紹反射相關的知識,爲了不和泛型類的方法混在一起,本文集中介紹基本類的反射知識。主要內容包括:
- 類的生命週期是什麼?
- 有幾種方式獲取類類型,這些方式之間的區別是什麼?
- 如何獲取類的超類或實現的接口?
- 如何獲取類及接口的訪問修飾符?
- 獲取類名的幾種方法及區別。
2. 正文
2.1 類的生命週期
類從被加載到虛擬機內存中開始,直到被卸載出內存爲止,它的整個生命週期包括:加載,驗證,準備,解析,初始化,使用和卸載這 7 個階段。其中,驗證,準備和解析這三個部分統稱爲連接(linking)。
加載(Loading): 這是類加載過程的第一個階段,在這個階段,虛擬機需要完成三件事情:通過一個類的全限定名來獲取定義這個類的二進制字節流;將這個二進制字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構;在 Java 堆中生成一個代表這個類的 java.lang.Class
對象,作爲方法區裏數據的訪問入口。
加載階段既可以使用系統提供的類加載器,也可以使用用戶自定義的類加載器。加載階段與連接階段的部分內容(如一部分字節碼文件格式驗證動作)是交叉進行的,因此加載階段尚未完成,連接階段可能已經開始。
需要注意的是,同一個類只會被類加載器加載一次。
驗證(Verification):這是連接階段的第一步,驗證是爲了確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。驗證包括:文件格式驗證,元數據驗證,字節碼驗證和符號引用驗證。
準備(Preparation):這個階段爲類的靜態變量分配內存並將其初始化爲默認值,這些內存都將在方法區中進行分配。
需要注意的是,準備階段不分配類中的實例變量的內存,因爲實例變量將會在對象實例化時隨着對象一起分配在 Java 堆中。
例如,
public static int value = 18;//在準備階段value初始值爲0 。在初始化階段纔會變爲18 。
解析(Resolution):這個階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。
初始化(Initialization):這是類加載過程的最後一步。
前面的類加載過程,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其餘動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的 Java 程序代碼。
初始化階段是執行類構造器<clinit>()
方法的過程。<clinit>()
方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{}
塊)中的語句合併產生的。
2.2 獲取類類型
這裏有一個 Fruit
類:
package com.java.advanced.features.reflect;
public class Fruit {
public String taste;
public Fruit() {
}
}
我們可以通過四種方式獲取 Fruit
類的類類型:
第一種方式:通過類對象獲取
Fruit fruit = new Fruit();
Class fruitClass1 = fruit.getClass();
System.out.println("fruitClass1 = " + fruitClass1);
打印信息:
fruitClass1 = class com.java.advanced.features.reflect.Fruit
第二種方式:通過類的 class
對象獲取
Class fruitClass2 = Fruit.class;
System.out.println("fruitClass2 = " + fruitClass2);
打印信息:
fruitClass2 = class com.java.advanced.features.reflect.Fruit
第三種方式:通過Class.forName(fullClassName)
獲取
Class fruitClass3 = null;
try {
fruitClass3 = Class.forName("com.java.advanced.feat
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("fruitClass3 = " + fruitClass3);
打印信息:
fruitClass3 = class com.java.advanced.features.reflect.Fruit
第四種方式:通過 Classloader.loadClass(fullClassName)
獲取
Class fruitClass4 = null;
try {
fruitClass4 = _01_GetClassTest.class.getClassLoader().loadClass("com.java.advanced.features.reflect.Fruit");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("fruitClass4 = " + fruitClass4);
從上面的打印信息,可以看到:這四種方式的打印信息是一樣的,但是這四種方式獲取到的類對象是否相等呢?
我們通過代碼驗證一下:
System.out.println("result = " + (fruitClass1 == fruitClass2
&& fruitClass2 == fruitClass3
&& fruitClass3 == fruitClass4));
打印結果:
result = true
它們是一模一樣的,這是因爲類只會被加載一次。
到這裏,我們知道了獲取類對象有 4 種方式,那麼這 4 種方式之間有什麼區別呢?
前兩種方式:通過類對象獲取和通過類的 class
對象獲取,不會拋出異常;後兩種方式:通過Class.forName(fullClassName)
獲取和通過 Classloader.loadClass(fullClassName)
獲取,可能會拋出 ClassNotFoundException
異常。
通過Class.forName(fullClassName)
獲取的 Class 是已經完成初始化的,通過 Classloader.loadClass(fullClassName)
獲取的 Class 是還沒有連接的。爲什麼有這樣的區別,我們需要去看一下源碼:
先看 Class.forName()
:
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
caller = Reflection.getCallerClass();
if (sun.misc.VM.isSystemDomainLoader(loader)) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}
從 forName(String name, boolean initialize, ClassLoader loader)
的方法文檔註釋裏,可以看到:
調用 Class.forName("Foo")
等價於調用 Class.forName("Foo", true, this.getClass().getClassLoader())
。
關鍵的就是第二個參數:boolean initialize
,表示這個類是否要被初始化。
結論:Class.forName(fullClassName)
方式表示類是被初始化的。
再看 ClassLoader
類的 loadClass()
方法:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
loadClass(String name)
調用的是 loadClass(String name, boolean resolve)
方法,第二個參數的值是 false
。boolean resolve
的意思是是否連接該類(Links the specified class)。
結論:Classloader.loadClass(fullClassName)
不會鏈接類。
如果加載的類依賴於初始化值的話,就需要採用 Class.forName(fullClassName)
的方式,而不能採用Classloader.loadClass(fullClassName)
方式。
2.3 獲取類名
查看文檔,獲取類名的方法有:
public String getName()
public String getSimpleName()
public String getCanonicalName()
public String getTypeName()
這 4 個方法的區別是什麼?它們分別適用於什麼場合呢?
爲了方便測試,這裏寫了一個泛型的打印方法:
private static <T> void print(Class<T> clazz, String label) {
System.out.println(label + ".getName() = " + clazz.getName());
System.out.println(label + ".getSimpleName() = " + clazz.getSimpleName());
System.out.println(label + ".getCanonicalName() = " + clazz.getCanonicalName())
System.out.println(label + ".getTypeName() = " + clazz.getTypeName());
System.out.println();
}
我們先通過簡單的 Fruit
類來說明:
print(Fruit.class, "simpleClass");
打印信息:
simpleClass.getName() = com.java.advanced.features.reflect.Fruit
simpleClass.getSimpleName() = Fruit
simpleClass.getCanonicalName() = com.java.advanced.features.reflect.Fruit
simpleClass.getTypeName() = com.java.advanced.features.reflect.Fruit
可以看到除了 getSimpleName()
外,其他三個打印信息是一樣的。
但是,它們是有區別的。
下面展示會出現區別的場景:
嵌套類場景:
嵌套類就是靜態內部類,大家不要把它和成員內部類混淆在一起了。
package com.java.advanced.features.reflect.basicclass;
public class OuterClass {
public static final class StaticInnerClass {
}
}
測試代碼:
print(OuterClass.StaticInnerClass.class, "staticInnerClassClass");
打印結果:
staticInnerClassClass.getName() = com.java.advanced.features.reflect.basicclass.OuterClass$StaticInnerClass
staticInnerClassClass.getSimpleName() = StaticInnerClass
staticInnerClassClass.getCanonicalName() = com.java.advanced.features.reflect.basicclass.OuterClass.StaticInnerClass
staticInnerClassClass.getTypeName() = com.java.advanced.features.reflect.basicclass.OuterClass$StaticInnerClass
可以看到,getCanonicalName()
獲取到的名字和 getName()
以及 getTypeName()
就不一樣了。
數組場景:
分別對一維和二維的字符串數組,打印相關信息:
print(String[].class, "1-arrayClass");
print(String[][].class, "2-arrayClass");
打印結果:
1-arrayClass.getName() = [Ljava.lang.String;
1-arrayClass.getSimpleName() = String[]
1-arrayClass.getCanonicalName() = java.lang.String[]
1-arrayClass.getTypeName() = java.lang.String[]
2-arrayClass.getName() = [[Ljava.lang.String;
2-arrayClass.getSimpleName() = String[][]
2-arrayClass.getCanonicalName() = java.lang.String[][]
2-arrayClass.getTypeName() = java.lang.String[][]
可以看到,getName()
獲取的名字和 getCanonicalName()
以及 getTypeName()
不同。
匿名內部類場景:
print(new Serializable() {
}.getClass(), "anonymousInnerClass");
打印結果:
anonymousInnerClass.getName() = com.java.advanced.features.reflect.basicclass._02_GetClassNamePackageNameTest$1
anonymousInnerClass.getSimpleName() =
anonymousInnerClass.getCanonicalName() = null
anonymousInnerClass.getTypeName() = com.java.advanced.features.reflect.basicclass._02_GetClassNamePackageNameTest$1
可以看到,匿名內部類通過 getSimpleName()
獲取到的是空字符串,通過 getCanonicalName()
獲取到的是 null
。
到這裏,對這 4 種獲取名字的方法做一下總結:
getName()
:返回的是虛擬機裏面的 class 的表示,是動態加載類所需要的,例如用於Class.forName(String name)
;
getCanonicalName()
:返回的是更容易理解的表示 用於獲取 import
的名字;
getSimpleName()
:獲取類名的標識符;
getTypeName()
: 如果不是數組,就調用 getName()
;是數組,就是類名後面跟維數([]),幾維就跟幾個[]。
2.4 獲取超類的 Class
對象
查看 Class
類的文檔有兩個函數:
// 獲取 Class 對象的普通類型父類
public native Class<? super T> getSuperclass();
// 獲取 Class 對象的泛型類型父類
public Type getGenericSuperclass()
下面通過例子說明 getSuperclass()
的使用。至於 getGenericSuperclass()
後面會介紹。
需要注意的是,不能把這裏的調用者 Class
對象侷限爲類對應的 Class
對象,還應該包括接口,基本類型以及 void
對應的 Class
對象。
這裏我們定義一個 Apple
類,它繼承 Fruit
類:
public class Fruit {
public String taste;
}
public class Apple extends Fruit {
}
測試代碼如下:
System.out.println("Apple.class.getSuperclass() = " + Apple.class.getSuperclass());
System.out.println("Collection.class.getSuperclass() = " + Collection.class.getSuperclass());
System.out.println("int.class.getSuperclass() = " + int.class.getSuperclass());
System.out.println("void.class.getSuperclass() = " + void.class.getSuperclass());
System.out.println("stringArray.getClass().getSuperclass() = " + String[].class.getSuperclass());
System.out.println("integerArray.getClass().getSuperclass() = " + Integer[].class.getSuperclass());
打印信息:
Apple.class.getSuperclass() = class com.java.advanced.features.reflect.Fruit
Collection.class.getSuperclass() = null
int.class.getSuperclass() = null
void.class.getSuperclass() = null
stringArray.getClass().getSuperclass() = class java.lang.Object
integerArray.getClass().getSuperclass() = class java.lang.Object
如果是類,就返回超類;
如果是接口,基本類型或 void
,就返回 null
;
如果是數組,就返回 Object
。
2.5 獲取直接繼承或實現的接口的 Class
對象列表
查看文檔,相關的函數有:
// 獲取 Class 對象的直接繼承或實現的接口 Class 對象列表
public Class<?>[] getInterfaces()
// 獲取 Class 對象的直接繼承或實現的泛型接口 Class 對象列表
public Type[] getGenericInterfaces()
下面通過例子說明 getInterfaces()
的使用。至於 getGenericInterfaces()
後面會介紹。
需要注意的是,不能把這裏的調用者 Class
對象侷限爲類對應的 Class
對象,還應該包括接口,基本類型以及 void
對應的 Class
對象。
還有一點需要注意的是,getInterfaces()
方法返回的是 Class<?>[]
,這是一個數組。這是因爲在 Java 中,一個類可以實現多個接口,一個接口可以繼承於多個接口。
爲了演示這個函數的使用,我們首先需要準備一些接口和類,並且爲了便於理解類的關係,會提供一個類圖出來。
public interface SuperPower {
}
public interface XRayVision extends SuperPower {
void seeThroughWalls();
}
public interface SuperHearing extends SuperPower {
void hearSubtleNoises();
}
public interface SuperSmell extends SuperPower {
void trackBySmell();
}
public interface SuperHearSmell extends SuperHearing, SuperSmell {
}
public interface Workable {
void work();
}
public class Man implements Workable {
@Override
public void work() {}
}
public class SuperHero extends Man implements XRayVision, SuperHearing, SuperSmell {
@Override
public void hearSubtleNoises() {}
@Override
public void trackBySmell() {}
@Override
public void seeThroughWalls() {}
}
類圖如下:
還需要封裝一個打印方法:
private static <T> void printGetInterfaces(Class<T> clazz) {
Class<?>[] interfaces = clazz.getInterfaces();
if (interfaces.length == 0) {
System.out.println("返回數組長度爲 0。");
System.out.println();
return;
}
for (Class<?> element : interfaces) {
System.out.println(element.getName());
}
System.out.println();
測試代碼:
對於 SuperHero
,這是一個繼承於 Man
類,並且實現了多個接口的類:
printGetInterfaces(SuperHero.class);
打印結果:
com.java.advanced.features.reflect.basicclass.XRayVision
com.java.advanced.features.reflect.basicclass.SuperHearing
com.java.advanced.features.reflect.basicclass.SuperSmell
關於打印結果,需要做一下說明:打印的順序就是 SuperHero
實現接口的順序:
public class SuperHero extends Man implements XRayVision, SuperHearing, SuperSmell
所以,我們可以根據索引來獲取對應的接口:
Class<?>[] interfaces = SuperHero.class.getInterfaces();
System.out.println("interfaces[0] = " + interfaces[0]);
System.out.println("interfaces[1] = " + interfaces[1]);
System.out.println("interfaces[2] = " + interfaces[2]);
打印信息:
interfaces[0] = interface com.java.advanced.features.reflect.basicclass.XRayVision
interfaces[1] = interface com.java.advanced.features.reflect.basicclass.SuperHearing
interfaces[2] = interface com.java.advanced.features.reflect.basicclass.SuperSmell
對於 SuperHearSmell
接口,它繼承了多個接口:
printGetInterfaces(SuperHearSmell.class);
打印信息:
com.java.advanced.features.reflect.basicclass.SuperHearin
com.java.advanced.features.reflect.basicclass.SuperSmell
對於基本類型及void
:
printGetInterfaces(int.class);
printGetInterfaces(void.class);
打印結果:
返回數組長度爲 0。
返回數組長度爲 0。
對於數組類型:
printGetInterfaces(String[].class);
打印結果:
java.lang.Cloneable
java.io.Serializable
總結一下:
對於實現了接口的類,會返回所實現接口的數組,數組中元素順序和實現順序一致;
對於繼承了接口的接口,會返回所直接繼承的接口數組,數組中元素順序和繼承順序一致;
對於沒有實現接口的類或沒有繼承接口的接口,會返回長度爲 0 的數組;
對於基本類型或 void
類型,會返回長度爲 0 的數組;
對於數組類型,會返回由 java.lang.Cloneable
和 java.io.Serializable
組成的數組。
2.6 獲取類修飾符
我們會遇到這樣的需求,判斷一個類是不是 pulibc
的?是不是 static
的?是不是 final
的?
這就需要獲取類修飾符。這需要進行兩步操作。
第一步要調用 Class
類的 getModifiers()
方法,返回 Class
對象以整數編碼的 Java 語言修飾符。
public native int getModifiers();
這個函數返回i一個 int
值,Class
對象包含的所有修飾符都打包在這個 int
值裏面了。
第二步要調用 Modifier
類的相關靜態函數,來解析上面得到的 int
值,或者判斷 int
值裏面是否有某個修飾符。
// 是否包含 public 修飾符
public static boolean isPublic(int mod)
// 是否包含 private 修飾符
public static boolean isPrivate(int mod)
// 是否包含 protected 修飾符
public static boolean isProtected(int mod)
// 是否包含 static 修飾符
public static boolean isStatic(int mod)
// 是否包含 final 修飾符
public static boolean isFinal(int mod)
// 是否包含 synchronized 修飾符
public static boolean isSynchronized(int mod)
// 是否包含 volatile 修飾符
public static boolean isVolatile(int mod)
// 是否包含 transient 修飾符
public static boolean isTransient(int mod)
// 是否包含 native 修飾符
public static boolean isNative(int mod)
// 是否包含 interface 修飾符
public static boolean isInterface(int mod)
// 是否包含 abstract 修飾符
public static boolean isAbstract(int mod)
// 是否包含 strictfp 修飾符
public static boolean isStrict(int mod)
// 解析參數 mod 對應的修飾符
public static String toString(int mod)
需要說明的一點是,Modifier
類不僅僅是應用於解析或判斷類修飾符,也包括接口修飾符,變量修飾符,方法修飾符。
下面我們看代碼演示:
測試類是 StaticInnerClass
:
public class OuterClass {
public static final class StaticInnerClass {
}
}
StaticInnerClass
包含 3 個修飾符:public
,static
和 final
。
測試代碼:
第一步:獲取打包所有修飾符的 int
值:
Class<OuterClass.StaticInnerClass> clazz = OuterClass.StaticInnerClass.class;
int modifiers = clazz.getModifiers();
System.out.println("modifiers = " + modifiers);
打印結果:
modifiers = 25
第二步,調用 Modifier
類的相關函數來解析上一步得到的 int
值或判斷是否包含某個修飾符。
System.out.println("Modifier.toString(modifiers) = " + Modifier.toString(modifiers));
System.out.println("Modifier.isPublic(modifiers) = " + Modifier.isPublic(modifiers));
System.out.println("Modifier.isStatic(modifiers) = " + Modifier.isStatic(modifiers));
System.out.println("Modifier.isFinal(modifiers) = " + Modifier.isFinal(modifiers));
打印結果:
Modifier.toString(modifiers) = public static final
Modifier.isPublic(modifiers) = true
Modifier.isStatic(modifiers) = true
Modifier.isFinal(modifiers) = true
我們再看一個接口的例子:
public interface OuterInterface {
public static interface InnerInterface {
}
}
獲取打包了所有修師符的 int
值:
int modifiers3 = OuterInterface.InnerInterface.class.getModifiers();
System.out.println("modifiers3 = " + modifiers3); // 打印:1545
解析 int
值或判斷是否包含某修飾符:
System.out.println("Modifier.toString(modifiers3) = " + Modifier.toString(modifiers3));
System.out.println("Modifier.isPublic(modifiers3) = " + Modifier.isPublic(modifiers3));
System.out.println("Modifier.isAbstract(modifiers3) = " + Modifier.isAbstract(modifiers3));
System.out.println("Modifier.isStatic(modifiers3) = " + Modifier.isStatic(modifiers3));
System.out.println("Modifier.isInterface(modifiers3) = " + Modifier.isInterface(modifiers3));
打印結果:
Modifier.toString(modifiers3) = public abstract static interface
Modifier.isPublic(modifiers3) = true
Modifier.isAbstract(modifiers3) = true
Modifier.isStatic(modifiers3) = true
Modifier.isInterface(modifiers3) = true
2.7 Class
對象的 isXXX()
這些方法比較容易掌握,這裏只是把演示代碼放出來:
@Deprecated
public class _09_ClassIsXXXMethodTest {
public static void main(String[] args) {
// isAnnotation() 是不是註解類型
System.out.println("Override.class.isAnnotation() = " + Override.class.isAnnotation());
// isAnnotationPresent(Class<? extends Annotation> annotationClass)
boolean annotationPresent = _09_ClassIsXXXMethodTest.class.isAnnotationPresent(Deprecated.class);
System.out.println("annotationPresent = " + annotationPresent);
// isAnonymousClass() 是不是匿名類
System.out.println("new Serializable(){}.getClass().isAnonymousClass() = " + new Serializable() {
}.getClass().isAnonymousClass());
// isArray() 是不是數組
System.out.println("String[].class.isArray() = " + String[].class.isArray());
// isAssignableFrom(Class<?> cls)
System.out.println("Collection.class.isAssignableFrom(List.class) = " + Collection.class.isAssignableFrom(List.class));
// isEnum()
System.out.println("Color.class.isEnum() = " + Color.class.isEnum());
// isInstance(Object obj)
List<String> list = new ArrayList<>();
System.out.println("Collection.class.isInstance(list) = " + Collection.class.isInstance(list));
// isInterface() 是不是接口
System.out.println("Serializable.class.isInterface() = " + Serializable.class.isInterface());
class A {
}
// isLocalClass() 是不是局部類,定義在方法內部的類
System.out.println("A.class.isLocalClass() = " + A.class.isLocalClass());
// 是不是成員類
System.out.println("B.class.isMemberClass() = " + B.class.isMemberClass());
// isPrimitive() 是不是原生類型
System.out.println("int.class.isPrimitive() = " + int.class.isPrimitive());
}
class B {
}
}
但是,下面的這兩個方法需要仔細區分一下,在實際開發中特別容易搞不清楚:
public native boolean isInstance(Object obj);
public native boolean isAssignableFrom(Class<?> cls);
isInstance(Object obj)
方法:判斷參數指定的對象是否是調用者 Class
對象所表示的類及其子類的實例。
這裏我們還是以 Apple
類來說明,它繼承 Fruit
類:
public class Fruit {
public String taste;
}
public class Apple extends Fruit {
}
測試代碼:
Apple apple = new Apple();
Fruit fruit = new Fruit();
System.out.println("Fruit.class.isInstance(apple) = " + Fruit.class.isInstance(apple));
System.out.println("Fruit.class.isInstance(fruit) = " + Fruit.class.isInstance(fruit));
運行結果:
Fruit.class.isInstance(apple) = true
Fruit.class.isInstance(fruit) = true
其實,isInstance()
方法和 instanceof
關鍵字作用是一樣的,我們來看一下使用 instanceof
關鍵字的寫法:
System.out.println("apple instanceof Fruit = " + (apple instanceof Fruit));
System.out.println("fruit instanceof Fruit = " + (fruit instanceof Fruit));
打印結果:
apple instanceof Fruit = true
fruit instanceof Fruit = true
接着,isAssignableFrom(Class<?> cls)
方法:它的參數是一個 Class
對象,用於判斷調用者Class
對象是否和參數Class
對象爲同一類型或者是參數 Class
對象的超類或超接口。
測試代碼:
boolean assignableFrom = Fruit.class.isAssignableFrom(Apple.class);
System.out.println("assignableFrom = " + assignableFrom);
運行結果:
assignableFrom = true
3. 最後
本文介紹了基本類的周邊信息獲取,希望能夠幫助到大家。鞏固好反射知識,對於閱讀框架源碼是很有幫助的。