Java 中文官方教程 2022 版(四十四)(3)https://developer.aliyun.com/article/1488321
检查枚举
原文:
docs.oracle.com/javase/tutorial/reflect/special/enumMembers.html
反射提供了三个特定于枚举的 API:
表示此类是否表示枚举类型
检索由枚举定义的枚举常量列表,按照它们声明的顺序
java.lang.reflect.Field.isEnumConstant()
表示此字段是否表示枚举类型的元素
有时需要动态检索枚举常量的列表;在非反射代码中,可以通过在枚举上调用隐式声明的静态方法 values()
来实现这一点。 如果枚举类型的实例不可用,则获取可能值列表的唯一方法是调用 Class.getEnumConstants()
,因为无法实例化枚举类型。
给定完全限定名称,EnumConstants
示例显示如何使用 Class.getEnumConstants()
检索枚举中常量的有序列表。
import java.util.Arrays; import static java.lang.System.out; enum Eon { HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC } public class EnumConstants { public static void main(String... args) { try { Class<?> c = (args.length == 0 ? Eon.class : Class.forName(args[0])); out.format("Enum name: %s%nEnum constants: %s%n", c.getName(), Arrays.asList(c.getEnumConstants())); if (c == Eon.class) out.format(" Eon.values(): %s%n", Arrays.asList(Eon.values())); // production code should handle this exception more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } }
输出示例如下。 用户输入以斜体显示。
$ *java EnumConstants java.lang.annotation.RetentionPolicy* Enum name: java.lang.annotation.RetentionPolicy Enum constants: [SOURCE, CLASS, RUNTIME]
$ *java EnumConstants java.util.concurrent.TimeUnit* Enum name: java.util.concurrent.TimeUnit Enum constants: [NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS]
该示例还表明,通过调用 Class.getEnumConstants()
返回的值与在枚举类型上调用 values()
返回的值相同。
$ *java EnumConstants* Enum name: Eon Enum constants: [HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC] Eon.values(): [HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC]
由于枚举是类,可以使用本教程中描述的字段、方法和构造函数部分中描述的相同反射 API 获取其他信息。 EnumSpy
代码示例说明了如何使用这些 API 获取有关枚举声明的其他信息。 该示例使用 Class.isEnum()
来限制要检查的类集。 它还使用 Field.isEnumConstant()
来区分枚举声明中的枚举常量和其他字段(并非所有字段都是枚举常量)。
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Member; import java.util.List; import java.util.ArrayList; import static java.lang.System.out; public class EnumSpy { private static final String fmt = " %11s: %s %s%n"; public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); if (!c.isEnum()) { out.format("%s is not an enum type%n", c); return; } out.format("Class: %s%n", c); Field[] flds = c.getDeclaredFields(); List<Field> cst = new ArrayList<Field>(); // enum constants List<Field> mbr = new ArrayList<Field>(); // member fields for (Field f : flds) { if (f.isEnumConstant()) cst.add(f); else mbr.add(f); } if (!cst.isEmpty()) print(cst, "Constant"); if (!mbr.isEmpty()) print(mbr, "Field"); Constructor[] ctors = c.getDeclaredConstructors(); for (Constructor ctor : ctors) { out.format(fmt, "Constructor", ctor.toGenericString(), synthetic(ctor)); } Method[] mths = c.getDeclaredMethods(); for (Method m : mths) { out.format(fmt, "Method", m.toGenericString(), synthetic(m)); } // production code should handle this exception more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } private static void print(List<Field> lst, String s) { for (Field f : lst) { out.format(fmt, s, f.toGenericString(), synthetic(f)); } } private static String synthetic(Member m) { return (m.isSynthetic() ? "[ synthetic ]" : ""); } }
$ *java 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. [ synthetic ] 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.lang.annotation.RetentionPolicy
的声明仅包含三个枚举常量。枚举常量暴露为public static final
字段。字段、构造函数和方法是由编译器生成的。$VALUES
字段与values()
方法的实现有关。
**注意:**出于各种原因,包括支持枚举类型的演变,枚举常量的声明顺序很重要。Class.getFields()
和Class.getDeclaredFields()
不能保证返回值的顺序与声明源代码中的顺序匹配。如果应用程序需要排序,请使用Class.getEnumConstants()
。
对于java.util.concurrent.TimeUnit
的输出显示,更复杂的枚举是可能的。这个类包括几个方法以及额外声明为static final
的字段,这些字段不是枚举常量。
$ java 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. [ synthetic ] Constructor: private java.util.concurrent.TimeUnit() Constructor: java.util.concurrent.TimeUnit (java.lang.String,int,java.util.concurrent.TimeUnit) [ synthetic ] Method: public static java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit.valueOf(java.lang.String) Method: public static java.util.concurrent.TimeUnit[] java.util.concurrent.TimeUnit.values() Method: public void java.util.concurrent.TimeUnit.sleep(long) throws java.lang.InterruptedException Method: public long java.util.concurrent.TimeUnit.toNanos(long) Method: public long java.util.concurrent.TimeUnit.convert (long,java.util.concurrent.TimeUnit) Method: abstract int java.util.concurrent.TimeUnit.excessNanos (long,long) Method: public void java.util.concurrent.TimeUnit.timedJoin (java.lang.Thread,long) throws java.lang.InterruptedException Method: public void java.util.concurrent.TimeUnit.timedWait (java.lang.Object,long) throws java.lang.InterruptedException Method: public long java.util.concurrent.TimeUnit.toDays(long) Method: public long java.util.concurrent.TimeUnit.toHours(long) Method: public long java.util.concurrent.TimeUnit.toMicros(long) Method: public long java.util.concurrent.TimeUnit.toMillis(long) Method: public long java.util.concurrent.TimeUnit.toMinutes(long) Method: public long java.util.concurrent.TimeUnit.toSeconds(long) Method: static long java.util.concurrent.TimeUnit.x(long,long,long)
使用枚举类型获取和设置字段
原文:
docs.oracle.com/javase/tutorial/reflect/special/enumSetGet.html
存储枚举的字段与任何其他引用类型一样设置和检索,使用Field.set()
和Field.get()
。有关访问字段的更多信息,请参阅本教程的 Fields 部分。
考虑一个需要在服务器应用程序中动态修改跟踪级别的应用程序,通常在运行时不允许此更改。假设服务器对象的实例可用。SetTrace
示例展示了代码如何将枚举的String
表示转换为枚举类型,并检索和设置存储枚举的字段的值。
import java.lang.reflect.Field; import static java.lang.System.out; enum TraceLevel { OFF, LOW, MEDIUM, HIGH, DEBUG } class MyServer { private TraceLevel level = TraceLevel.OFF; } public class SetTrace { public static void main(String... args) { TraceLevel newLevel = TraceLevel.valueOf(args[0]); try { MyServer svr = new MyServer(); Class<?> c = svr.getClass(); Field f = c.getDeclaredField("level"); f.setAccessible(true); TraceLevel oldLevel = (TraceLevel)f.get(svr); out.format("Original trace level: %s%n", oldLevel); if (oldLevel != newLevel) { f.set(svr, newLevel); out.format(" New trace level: %s%n", f.get(svr)); } // production code should handle these exceptions more gracefully } catch (IllegalArgumentException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } catch (NoSuchFieldException x) { x.printStackTrace(); } } }
由于枚举常量是单例,可以使用==
和!=
运算符来比较相同类型的枚举常量。
$ *java SetTrace OFF* Original trace level: OFF $ *java SetTrace DEBUG* Original trace level: OFF New trace level: DEBUG
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/special/enumTrouble.html
以下示例展示了在使用枚举类型时可能遇到的问题。
尝试实例化枚举类型时出现 IllegalArgumentException
正如前面提到的,实例化枚举类型是被禁止的。EnumTrouble
示例尝试这样做。
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import static java.lang.System.out; enum Charge { POSITIVE, NEGATIVE, NEUTRAL; Charge() { out.format("under construction%n"); } } public class EnumTrouble { public static void main(String... args) { try { Class<?> c = Charge.class; Constructor[] ctors = c.getDeclaredConstructors(); for (Constructor ctor : ctors) { out.format("Constructor: %s%n", ctor.toGenericString()); ctor.setAccessible(true); ctor.newInstance(); } // production code should handle these exceptions more gracefully } catch (InstantiationException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } catch (InvocationTargetException x) { x.printStackTrace(); } } }
$ *java EnumTrouble* Constructor: private Charge() Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:511) at EnumTrouble.main(EnumTrouble.java:22)
提示: 明确实例化枚举是一种编译时错误,因为这将阻止定义的枚举常量保持唯一。这种限制也在反射代码中执行。试图使用默认构造函数实例化类的代码应该首先调用Class.isEnum()
来确定该类是否为枚举。
设置具有不兼容枚举类型的字段时出现 IllegalArgumentException
存储枚举的字段应该设置为适当的枚举类型。(实际上,任何类型的字段都必须设置为兼容的类型。)EnumTroubleToo
示例会产生预期的错误。
import java.lang.reflect.Field; enum E0 { A, B } enum E1 { A, B } class ETest { private E0 fld = E0.A; } public class EnumTroubleToo { public static void main(String... args) { try { ETest test = new ETest(); Field f = test.getClass().getDeclaredField("fld"); f.setAccessible(true); f.set(test, E1.A); // IllegalArgumentException // production code should handle these exceptions more gracefully } catch (NoSuchFieldException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } } }
$ *java EnumTroubleToo* Exception in thread "main" java.lang.IllegalArgumentException: Can not set E0 field ETest.fld to E1 at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException (UnsafeFieldAccessorImpl.java:146) at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException (UnsafeFieldAccessorImpl.java:150) at sun.reflect.UnsafeObjectFieldAccessorImpl.set (UnsafeObjectFieldAccessorImpl.java:63) at java.lang.reflect.Field.set(Field.java:657) at EnumTroubleToo.main(EnumTroubleToo.java:16)
提示: 严格来说,将类型为X
的字段设置为类型为Y
的值只有在以下语句成立时才能成功:
X.class.isAssignableFrom(Y.class) == true
代码可以修改以执行以下测试,以验证类型是否兼容:
if (f.getType().isAssignableFrom(E0.class)) // compatible else // expect IllegalArgumentException