這篇Java教程基于JDK1.8。教程中的示例和實踐不會使用未來發行版中的優化建議。
教程:反射之數組&枚舉
數組和枚舉 該教程還展示了兩種特殊類型:數組,它是在運作期生成的;枚舉,定義唯一的已命名對象執行個體。示例代碼示範了如何擷取數組類型以及如何從數組和枚舉中設定和擷取字段的值。
站在Java虛拟機的角度上來看,數組和枚舉其實都是類。Class 上的很多方法都可以用在數組和枚舉上。反射也為數組和枚舉提供了一些特定API。該課程将通過一些代碼示例來示範如何将數組、枚舉和其他一些類進行區分以及如何操作它們。
數組
- 确定數組類型
通過 Class.isArray() 來确定一個數組類型。ArrayFind 示例示範了如何确定給定類字段是否是數組類型以及它的每一個元件類型。
$ java reflection/ArrayFind java.nio.ByteBuffer
final byte[] java.nio.ByteBuffer.hb
Field: hb
Class: class [B
Component Type : byte
$ java reflection/ArrayFind java.lang.Throwable
private static final java.lang.StackTraceElement[] java.lang.Throwable.UNASSIGNED_STACK
Field: UNASSIGNED_STACK
Class: class [Ljava.lang.StackTraceElement;
Component Type : class java.lang.StackTraceElement
private java.lang.StackTraceElement[] java.lang.Throwable.stackTrace
Field: stackTrace
Class: class [Ljava.lang.StackTraceElement;
Component Type : class java.lang.StackTraceElement
private static final java.lang.Throwable[] java.lang.Throwable.EMPTY_THROWABLE_ARRAY
Field: EMPTY_THROWABLE_ARRAY
Class: class [Ljava.lang.Throwable;
Component Type : class java.lang.Throwable
$ java reflection/ArrayFind java.awt.Cursor
protected static java.awt.Cursor[] java.awt.Cursor.predefined
Field: predefined
Class: class [Ljava.awt.Cursor;
Component Type : class java.awt.Cursor
private static final java.awt.Cursor[] java.awt.Cursor.predefinedPrivate
Field: predefinedPrivate
Class: class [Ljava.awt.Cursor;
Component Type : class java.awt.Cursor
static final java.lang.String[][] java.awt.Cursor.cursorProperties
Field: cursorProperties
Class: class [[Ljava.lang.String;
Component Type : class [Ljava.lang.String;
- 建立新數組
ArrayCreator 示範了如何通過反射API來建立并設定數組元素。
$ java reflection/ArrayCreator
java.math.BigInteger[] = [123, 234, 345]
- 擷取并設定數組以及它們的元件
就像在非反射代碼中一樣,數組字段可以全部設定或逐個元件檢索。使用 java.lang.reflect.Field.set(Object obj, Object value) 來設定整個數組,使用Field.get(Object) 來擷取整個數組。可以使用 java.lang.reflect.Array 中的方法設定或檢索單個元件。
Array 提供了set()函數和get()函數方法,用于設定和擷取任何基本類型的元件。比如:通過Array.setInt(Object array, int index, int value)來設定一個int數組中的元素,通過Array.getInt(Object array, int index) 來擷取一個int數組中的元素。
這些方法支援自動擴充資料類型。是以,Array.setShort()可以用來設定int數組的值,因為16位的short可以擴充為32位的int,而不會丢失資料;另一方面,在int數組上調用Array.setLong() 将引發IllegalArgumentException,因為不能将64位long縮小到32位int中存儲而不丢失資訊。無論傳遞的實際值是否可以在目标資料類型中準确表示,這都是正确的。
引用類型的數組采用Array.set(Object array, int index, int value)和Array.get(Object array, int index) 來設定并擷取值。
設定類型為數組的字段
GrowBufferedReader 示範了如何替換數組類型字段的值。(該示例假設建立原始BufferedReader的代碼是不可修改的;否則,簡單地使用替代構造函數BufferedReader(java.io)就很簡單了。它接受一個輸入緩沖區大小。)
$ java reflection/GrowBufferedReader grow
Using new backing array, size = 16384
$ java reflection/GrowBufferedReader
Using original backing array , size = 8192
通路多元數組的元素
多元數組可以簡化為内嵌數組。一個二維數組其實就是一個數組的數組。三維數組是一個二維數組的數組,以此類推。CreateMatrix 示例示範了如何使用反射建立并初始化一個多元數組。
$ java reflection/CreateMatrix
matrix[0][0] = 1
matrix[0][1] = 2
matrix[1][0] = 3
matrix[1][1] = 4
- 故障排查
以下示例示範了操作數組時可能發生的典型錯誤。
不可轉換類型導緻的IllegalArgumentException
ArrayTroubleAgain 示例将生成一個IllegalArgumentException異常。
$ java reflection/ArrayTroubleAgain
Exception in thread "main" java.lang.IllegalArgumentException: Argument is not an array
at java.lang.reflect.Array.setInt(Native Method)
at reflection.ArrayTroubleAgain.main(ArrayTroubleAgain.java:15)
空數組的ArrayIndexOutOfBoundsException
ArrayTrouble 例子說明如果試圖通路一個零長度數組的元素,将會發生錯誤。
$ java reflection/ArrayTrouble
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at java.lang.reflect.Array.get(Native Method)
at reflection.ArrayTrouble.main(ArrayTrouble.java:17)
嘗試類型縮窄導緻的IllegalArgumentException
ArrayTroubleToo 嘗試執行可能丢失資料的操作将會報錯。
$ java reflection/ArrayTroubleToo
Exception in thread "main" java.lang.IllegalArgumentException: argument type mismatch
at java.lang.reflect.Array.setLong(Native Method)
at reflection.ArrayTroubleToo.main(ArrayTroubleToo.java:14)
枚舉
在反射中會将枚舉類當做字典類來對待。Class.isEnum(https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#isEnum–)可以用來判斷一個類是否是枚舉。Class.getEnumConstants() 可以用來擷取枚舉中定義的常量。java.lang.reflect.Field.isEnumConstant() 用來判斷一個字段是否是枚舉類型。
- 枚舉檢測
反射為枚舉提供了3個特有的API:
Class.isEnum() 指明一個類是否是枚舉類型
Class.getEnumConstants() 擷取枚舉中聲明的枚舉常量清單
java.lang.reflect.Field.isEnumConstant() 訓示此字段是否表示枚舉類型的元素
有時需要動态檢索枚舉常量清單。在非反射代碼中,可以通過調用Enum上隐式聲明的values()靜态方法來實作。如果枚舉類型執行個體不可用,則擷取枚舉值的唯一方法是調用Class.getEnumConstants() ,因為枚舉類型無法執行個體化。
EnumConstants 執行個體示範了如何根據類的全限定類名來擷取枚舉常量清單。
$ java reflection/EnumConstants java.lang.annotation.RetentionPolicy
Enum Names java.lang.annotation.RetentionPolicy ,Enum Constants [SOURCE, CLASS, RUNTIME]
$ java reflection/EnumConstants java.util.concurrent.TimeUnit
Enum Names java.util.concurrent.TimeUnit ,Enum Constants [NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]
$ java reflection/EnumConstants
Enum Names reflection.Eon ,Enum Constants [HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC]
Eon.values [HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC]
EnumSpy 示例代碼示範了如何使用API來擷取枚舉聲明的相關資訊。
$ java reflection/EnumSpy java.lang.annotation.RetentionPolicy
Class : class java.lang.annotation.RetentionPolicy
Constant: public static final java.lang.annotation.RetentionPolicy java.lang.annotation.RetentionPolicy.SOURCE
Constant: public static final java.lang.annotation.RetentionPolicy java.lang.annotation.RetentionPolicy.CLASS
Constant: public static final java.lang.annotation.RetentionPolicy java.lang.annotation.RetentionPolicy.RUNTIME
Field: private static final java.lang.annotation.RetentionPolicy[] java.lang.annotation.RetentionPolicy.$VALUES [ senthetic ]
Constructor: private java.lang.annotation.RetentionPolicy()
Method: public static java.lang.annotation.RetentionPolicy[] java.lang.annotation.RetentionPolicy.values()
Method: public static java.lang.annotation.RetentionPolicy java.lang.annotation.RetentionPolicy.valueOf(java.lang.String)
$ java reflection/EnumSpy java.util.concurrent.TimeUnit
Class : class java.util.concurrent.TimeUnit
Constant: public static final java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.NANOSECONDS
Constant: public static final java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.MICROSECONDS
Constant: public static final java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.MILLISECONDS
Constant: public static final java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.SECONDS
Constant: public static final java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.MINUTES
Constant: public static final java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.HOURS
Constant: public static final java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.DAYS
Field: static final long java.util.concurrent.TimeUnit.C0
Field: static final long java.util.concurrent.TimeUnit.C1
Field: static final long java.util.concurrent.TimeUnit.C2
Field: static final long java.util.concurrent.TimeUnit.C3
Field: static final long java.util.concurrent.TimeUnit.C4
Field: static final long java.util.concurrent.TimeUnit.C5
Field: static final long java.util.concurrent.TimeUnit.C6
Field: static final long java.util.concurrent.TimeUnit.MAX
Field: private static final java.util.concurrent.TimeUnit[] java.util.concurrent.TimeUnit.$VALUES [ senthetic ]
Constructor: private java.util.concurrent.TimeUnit()
Constructor: java.util.concurrent.TimeUnit(java.lang.String,int,java.util.concurrent.TimeUnit$1) [ senthetic ]
Method: public static java.util.concurrent.TimeUnit[] java.util.concurrent.TimeUnit.values()
Method: public static java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.valueOf(java.lang.String)
Method: public void java.util.concurrent.TimeUnit.sleep(long) throws java.lang.InterruptedException
Method: static long java.util.concurrent.TimeUnit.x(long,long,long)
Method: public long java.util.concurrent.TimeUnit.toNanos(long)
Method: public long java.util.concurrent.TimeUnit.convert(long,java.util.concurrent.TimeUnit)
Method: public long java.util.concurrent.TimeUnit.toMillis(long)
Method: public long java.util.concurrent.TimeUnit.toMicros(long)
Method: public long java.util.concurrent.TimeUnit.toSeconds(long)
Method: public long java.util.concurrent.TimeUnit.toMinutes(long)
Method: public long java.util.concurrent.TimeUnit.toHours(long)
Method: public long java.util.concurrent.TimeUnit.toDays(long)
Method: abstract int java.util.concurrent.TimeUnit.excessNanos(long,long)
Method: public void java.util.concurrent.TimeUnit.timedWait(java.lang.Object,long) throws java.lang.InterruptedException
Method: public void java.util.concurrent.TimeUnit.timedJoin(java.lang.Thread,long) throws java.lang.InterruptedException
- 擷取、設定枚舉類型的字段
存取枚舉類型的字段跟其他引用類型類似,使用Field.set() 和 Field.get()
SetTrace 示例示範了如何将字元串形式的枚舉表示轉換成枚舉類型,以及如何存取該枚舉字段。
$ java reflection/SetTrace OFF
Original tracelevel is OFF
$ java reflection/SetTrace DEBUG
Original tracelevel is OFF
New tracelevel is DEBUG
- 故障排查
以下示例顯示了在使用枚舉時可能遇到的問題。
- 嘗試執行個體化枚舉時抛出IllegalArgumentException
EnumTrouble 展示了執行個體化枚舉時抛出的錯誤。
$ java reflection/EnumTrouble
Constructor : private reflection.Charge()
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at reflection.EnumTrouble.main(EnumTrouble.java:33)
- 為枚舉類型設定一個不合理的枚舉類型時抛出IllegalArgumentException
存儲枚舉類型的字段都有合适的類型。EnumTroubleToo 示範了預期的錯誤。
$ java reflection/EnumTroubleToo
Exception in thread "main" java.lang.IllegalArgumentException: Can not set reflection.E0 field reflection.ETest.fld to reflection.E1
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
at java.lang.reflect.Field.set(Field.java:764)
at reflection.EnumTroubleToo.main(EnumTroubleToo.java:25)