天天看点

反射-数组&枚举

这篇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)