Java 中文官方教程 2022 版(四十三)(1)https://developer.aliyun.com/article/1488247
以下是一些输出示例。用户输入用斜体表示。
$ *java ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap* Class: java.util.concurrent.ConcurrentNavigableMap Modifiers: public abstract interface Type Parameters: K V Implemented Interfaces: java.util.concurrent.ConcurrentMap<K, V> java.util.NavigableMap<K, V> Inheritance Path: -- No Super Classes -- Annotations: -- No Annotations --
这是源代码中java.util.concurrent.ConcurrentNavigableMap
的实际声明:
public interface ConcurrentNavigableMap<K,V> extends ConcurrentMap<K,V>, NavigableMap<K,V>
请注意,由于这是一个接口,它隐式地是abstract
的。编译器为每个接口添加这个修饰符。此外,此声明包含两个泛型类型参数,K
和V
。示例代码仅打印这些参数的名称,但可以使用java.lang.reflect.TypeVariable
中的方法检索有关它们的其他信息。接口也可以像上面显示的那样实现其他接口。
$ *java ClassDeclarationSpy "[Ljava.lang.String;"* Class: java.lang.String[] Modifiers: public abstract final Type Parameters: -- No Type Parameters -- Implemented Interfaces: interface java.lang.Cloneable interface java.io.Serializable Inheritance Path: java.lang.Object Annotations: -- No Annotations --
由于数组是运行时对象,所有类型信息都由 Java 虚拟机定义。特别是,数组实现了Cloneable
和java.io.Serializable
,它们的直接超类始终是Object
。
$ *java ClassDeclarationSpy java.io.InterruptedIOException* Class: java.io.InterruptedIOException Modifiers: public Type Parameters: -- No Type Parameters -- Implemented Interfaces: -- No Implemented Interfaces -- Inheritance Path: java.io.IOException java.lang.Exception java.lang.Throwable java.lang.Object Annotations: -- No Annotations -- • 22
从继承路径可以推断出java.io.InterruptedIOException
是一个受检异常,因为RuntimeException
不存在。
$ *java ClassDeclarationSpy java.security.Identity* Class: java.security.Identity Modifiers: public abstract Type Parameters: -- No Type Parameters -- Implemented Interfaces: interface java.security.Principal interface java.io.Serializable Inheritance Path: java.lang.Object Annotations: @java.lang.Deprecated()
此输出显示java.security.Identity
,一个已弃用的 API,具有注解java.lang.Deprecated
。这可能被反射代码用于检测已弃用的 API。
注意: 并非所有注解都可以通过反射获得。只有那些具有java.lang.annotation.RetentionPolicy
为RUNTIME
的注解是可访问的。在语言中预定义的三个注解@Deprecated
,@Override
,和@SuppressWarnings
中,只有@Deprecated
在运行时可用。
发现类成员
原文:
docs.oracle.com/javase/tutorial/reflect/class/classMembers.html
Class
提供了两类方法来访问字段、方法和构造函数:列举这些成员的方法和搜索特定成员的方法。此外,还有用于访问直接在类上声明的成员的方法,以及搜索超接口和超类以查找继承成员的方法。以下表格总结了所有定位成员的方法及其特性。
定位字段的类方法
Class API |
成员列表? | 继承成员? | 私有成员? |
getDeclaredField() |
否 | 否 | 是 |
getField() |
否 | 是 | 否 |
getDeclaredFields() |
是 | 否 | 是 |
getFields() |
是 | 是 | 否 |
定位方法的类方法
Class API |
成员列表? | 继承成员? | 私有成员? |
getDeclaredMethod() |
否 | 否 | 是 |
getMethod() |
否 | 是 | 否 |
getDeclaredMethods() |
是 | 否 | 是 |
getMethods() |
是 | 是 | 否 |
定位构造函数的类方法
Class API |
成员列表? | 继承成员? | 私有成员? |
getDeclaredConstructor() |
否 | N/A¹ | 是 |
getConstructor() |
否 | N/A¹ | 否 |
getDeclaredConstructors() |
是 | N/A¹ | 是 |
getConstructors() |
是 | N/A¹ | 否 |
¹ 构造函数不会被继承。
给定一个类名和感兴趣的成员指示,ClassSpy
示例使用get*s()
方法来确定所有公共元素的列表,包括任何继承的元素。
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Member; import static java.lang.System.out; enum ClassMember { CONSTRUCTOR, FIELD, METHOD, CLASS, ALL } public class ClassSpy { public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); out.format("Class:%n %s%n%n", c.getCanonicalName()); Package p = c.getPackage(); out.format("Package:%n %s%n%n", (p != null ? p.getName() : "-- No Package --")); for (int i = 1; i < args.length; i++) { switch (ClassMember.valueOf(args[i])) { case CONSTRUCTOR: printMembers(c.getConstructors(), "Constructor"); break; case FIELD: printMembers(c.getFields(), "Fields"); break; case METHOD: printMembers(c.getMethods(), "Methods"); break; case CLASS: printClasses(c); break; case ALL: printMembers(c.getConstructors(), "Constuctors"); printMembers(c.getFields(), "Fields"); printMembers(c.getMethods(), "Methods"); printClasses(c); break; default: assert false; } } // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } private static void printMembers(Member[] mbrs, String s) { out.format("%s:%n", s); for (Member mbr : mbrs) { if (mbr instanceof Field) out.format(" %s%n", ((Field)mbr).toGenericString()); else if (mbr instanceof Constructor) out.format(" %s%n", ((Constructor)mbr).toGenericString()); else if (mbr instanceof Method) out.format(" %s%n", ((Method)mbr).toGenericString()); } if (mbrs.length == 0) out.format(" -- No %s --%n", s); out.format("%n"); } private static void printClasses(Class<?> c) { out.format("Classes:%n"); Class<?>[] clss = c.getClasses(); for (Class<?> cls : clss) out.format(" %s%n", cls.getCanonicalName()); if (clss.length == 0) out.format(" -- No member interfaces, classes, or enums --%n"); out.format("%n"); } }
这个例子相对紧凑;然而,printMembers()
方法略显笨拙,因为java.lang.reflect.Member
接口自反射最早的实现以来就存在,当引入泛型时,它无法被修改以包含更有用的getGenericString()
方法。唯一的替代方法是像所示那样进行测试和转换,用printConstructors()
、printFields()
和printMethods()
替换此方法,或者满足于相对简洁的Member.getName()
的结果。
输出示例及其解释如下。用户输入用斜体表示。
$ *java ClassSpy java.lang.ClassCastException CONSTRUCTOR* Class: java.lang.ClassCastException Package: java.lang Constructor: public java.lang.ClassCastException() public java.lang.ClassCastException(java.lang.String)
由于构造函数不会被继承,因此在直接超类RuntimeException
和其他超类中定义的异常链接机制构造函数(具有Throwable
参数)将不会被找到。
$ *java ClassSpy java.nio.channels.ReadableByteChannel METHOD* Class: java.nio.channels.ReadableByteChannel Package: java.nio.channels Methods: public abstract int java.nio.channels.ReadableByteChannel.read (java.nio.ByteBuffer) throws java.io.IOException public abstract void java.nio.channels.Channel.close() throws java.io.IOException public abstract boolean java.nio.channels.Channel.isOpen()
接口java.nio.channels.ReadableByteChannel
定义了read()
。其余方法是从超级接口继承的。可以通过将get*s()
替换为getDeclared*s()
来轻松修改此代码,仅列出实际在类中声明的方法。
$ *java ClassSpy ClassMember FIELD METHOD* Class: ClassMember Package: -- No Package -- Fields: public static final ClassMember ClassMember.CONSTRUCTOR public static final ClassMember ClassMember.FIELD public static final ClassMember ClassMember.METHOD public static final ClassMember ClassMember.CLASS public static final ClassMember ClassMember.ALL Methods: public static ClassMember ClassMember.valueOf(java.lang.String) public static ClassMember[] ClassMember.values() public final int java.lang.Enum.hashCode() public final int java.lang.Enum.compareTo(E) public int java.lang.Enum.compareTo(java.lang.Object) public final java.lang.String java.lang.Enum.name() public final boolean java.lang.Enum.equals(java.lang.Object) public java.lang.String java.lang.Enum.toString() public static <T> T java.lang.Enum.valueOf (java.lang.Class<T>,java.lang.String) public final java.lang.Class<E> java.lang.Enum.getDeclaringClass() public final int java.lang.Enum.ordinal() public final native java.lang.Class<?> java.lang.Object.getClass() public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final void java.lang.Object.wait() hrows java.lang.InterruptedException public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll()
在这些结果的字段部分,枚举常量被列出。虽然这些在技术上是字段,但将它们与其他字段区分开可能是有用的。这个例子可以修改为使用java.lang.reflect.Field.isEnumConstant()
来实现这一目的。在本教程的后续部分检查枚举中的EnumSpy
示例包含了一个可能的实现。
在输出的方法部分中,观察到方法名称包含声明类的名称。因此,toString()
方法是由Enum
实现的,而不是从Object
继承的。可以通过使用Field.getDeclaringClass()
来修改代码,使这一点更明显。以下片段展示了潜在解决方案的一部分。
if (mbr instanceof Field) { Field f = (Field)mbr; out.format(" %s%n", f.toGenericString()); out.format(" -- declared in: %s%n", f.getDeclaringClass()); }
故障排除
原文:
docs.oracle.com/javase/tutorial/reflect/class/classTrouble.html
以下示例展示了在反射类时可能遇到的典型错误。
编译器警告:“注意:…使用了未经检查或不安全的操作”
当调用方法时,会检查参数值的类型并可能进行转换。ClassWarning
调用getMethod()
会导致典型的未经检查的转换警告:
import java.lang.reflect.Method; public class ClassWarning { void m() { try { Class c = ClassWarning.class; Method m = c.getMethod("m"); // warning // production code should handle this exception more gracefully } catch (NoSuchMethodException x) { x.printStackTrace(); } } }
$ *javac ClassWarning.java* Note: ClassWarning.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. $ *javac -Xlint:unchecked ClassWarning.java* ClassWarning.java:6: warning: [unchecked] unchecked call to getMethod (String,Class<?>...) as a member of the raw type Class Method m = c.getMethod("m"); // warning ^ 1 warning
许多库方法已经使用了泛型声明,包括Class
中的几个方法。由于c
声明为原始类型(没有类型参数),并且getMethod()
的相应参数是参数化类型,因此会发生未经检查的转换。编译器需要生成警告。(参见Java 语言规范,Java SE 7 版,章节未经检查的转换和方法调用转换。)
有两种可能的解决方案。更可取的是修改c
的声明以包含适当的通用类型。在这种情况下,声明应为:
Class<?> c = warn.getClass();
或者,可以在有问题的语句之前使用预定义的注释@SuppressWarnings
来明确抑制警告。
Class c = ClassWarning.class; @SuppressWarnings("unchecked") Method m = c.getMethod("m"); // warning gone
提示: 作为一个一般原则,不应忽略警告,因为它们可能表明存在错误。应适当使用参数化声明。如果不可能(也许是因为应用程序必须与库供应商的代码交互),则可以使用@SuppressWarnings
对有问题的行进行注释。
当构造函数不可访问时会出现 InstantiationException
Class.newInstance()
如果尝试创建一个类的新实例且零参数构造函数不可见,则会抛出InstantiationException
。ClassTrouble
示例展示了生成的堆栈跟踪。
class Cls { private Cls() {} } public class ClassTrouble { public static void main(String... args) { try { Class<?> c = Class.forName("Cls"); c.newInstance(); // InstantiationException // production code should handle these exceptions more gracefully } catch (InstantiationException x) { x.printStackTrace(); } catch (IllegalAccessException x) { x.printStackTrace(); } catch (ClassNotFoundException x) { x.printStackTrace(); } } }
$ *java ClassTrouble* java.lang.IllegalAccessException: Class ClassTrouble can not access a member of class Cls with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65) at java.lang.Class.newInstance0(Class.java:349) at java.lang.Class.newInstance(Class.java:308) at ClassTrouble.main(ClassTrouble.java:9)
Class.newInstance()
的行为非常类似于new
关键字,并且会因为与new
相同的原因而失败。在反射中的典型解决方案是利用java.lang.reflect.AccessibleObject
类,该类提供了抑制访问控制检查的能力;然而,这种方法不起作用,因为java.lang.Class
不扩展AccessibleObject
。唯一的解决方案是修改代码以使用Constructor.newInstance()
,该方法确实扩展了AccessibleObject
。
提示: 一般来说,最好使用Constructor.newInstance()
,原因在创建新类实例部分的成员课程中有描述。
使用Constructor.newInstance()
可能会出现潜在问题的其他示例,可以在构造函数故障排除部分的成员课程中找到。
课程:成员
原文:
docs.oracle.com/javase/tutorial/reflect/member/index.html
反射定义了一个接口java.lang.reflect.Member
,该接口由java.lang.reflect.Field
、java.lang.reflect.Method
和java.lang.reflect.Constructor
实现。这些对象将在本课程中讨论。对于每个成员,本课程将描述相关的 API 以检索声明和类型信息,成员特有的任何操作(例如,设置字段的值或调用方法),以及常见的错误。每个概念将通过代码示例和相关输出进行说明,这些输出近似一些预期的反射用法。
注意: 根据Java 语言规范,Java SE 7 版,类的成员是类主体的继承组件,包括字段、方法、嵌套类、接口和枚举类型。由于构造函数不会被继承,因此它们不是成员。这与java.lang.reflect.Member
的实现类有所不同。
字段
字段具有类型和值。java.lang.reflect.Field
类提供了用于访问类型信息以及在给定对象上设置和获取字段值的方法。
- 获取字段类型 描述了如何获取字段的声明类型和泛型类型
- 检索和解析字段修饰符 展示了如何获取字段声明的部分,如
public
或transient
- 获取和设置字段值 说明了如何访问字段的值
- 故障排除 描述了可能导致混淆的一些常见编码错误
方法
方法具有返回值、参数,并可能抛出异常。java.lang.reflect.Method
类提供了用于获取参数和返回值的类型信息的方法。它还可以用于在给定对象上调用方法。
- 获取方法类型信息 展示了如何枚举类中声明的方法并获取类型信息
- 获取方法参数的名称 展示了如何检索方法或构造函数的参数的名称和其他信息
- 检索和解析方法修饰符描述了如何访问和解码与方法相关的修饰符和其他信息
- 调用方法说明了如何执行一个方法并获得其返回值
- 故障排除涵盖了在查找或调用方法时遇到的常见错误
构造函数
构造函数的反射 API 在java.lang.reflect.Constructor
中定义,与方法的 API 类似,但有两个主要例外:首先,构造函数没有返回值;其次,调用构造函数会为给定类创建一个新的对象实例。
- 查找构造函数说明了如何检索具有特定参数的构造函数
- 检索和解析构造函数修饰符展示了如何获取构造函数声明的修饰符以及有关构造函数的其他信息
- 创建新的类实例展示了如何通过调用其构造函数来实例化一个对象的实例
- 故障排除描述了在查找或调用构造函数时可能遇到的常见错误
字段
原文:
docs.oracle.com/javase/tutorial/reflect/member/field.html
一个字段是一个具有关联值的类、接口或枚举。java.lang.reflect.Field
类中的方法可以检索有关字段的信息,比如它的名称、类型、修饰符和注解。(检查类修饰符和类型章节中的类课程描述了如何检索注解。)还有一些方法可以实现对字段值的动态访问和修改。这些任务在以下章节中介绍:
- 获取字段类型描述了如何获取字段的声明类型和泛型类型
- 检索和解析字段修饰符展示了如何获取字段声明的部分,比如
public
或transient
- 获取和设置字段值说明了如何访问字段值
- 故障排除描述了可能导致混淆的一些常见编码错误
当编写一个应用程序,比如一个类浏览器时,找出哪些字段属于特定类可能会很有用。通过调用Class.getFields()
来识别类的字段。getFields()
方法返回一个包含每个可访问的公共字段的Field
对象数组。
如果一个公共字段是以下任一成员,则可以访问它:
- 这个类
- 这个类的一个超类
- 由这个类实现的接口
- 由这个类实现的接口扩展的一个接口
一个字段可以是一个类(实例)字段,比如java.io.Reader.lock
,一个静态字段,比如java.lang.Integer.MAX_VALUE
,或一个枚举常量,比如java.lang.Thread.State.WAITING
。
获取字段类型
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldTypes.html
字段可以是原始类型或引用类型。有八种原始类型:boolean
、byte
、short
、int
、long
、char
、float
和 double
。引用类型是任何直接或间接是 java.lang.Object
的子类,包括接口、数组和枚举类型。
FieldSpy
示例根据完全限定的二进制类名和字段名打印字段的类型和泛型类型。
import java.lang.reflect.Field; import java.util.List; public class FieldSpy<T> { public boolean[][] b = {{ false, false }, { true, true } }; public String name = "Alice"; public List<Integer> list; public T val; public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); Field f = c.getField(args[1]); System.out.format("Type: %s%n", f.getType()); System.out.format("GenericType: %s%n", f.getGenericType()); // production code should handle these exceptions more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } catch (NoSuchFieldException x) { x.printStackTrace(); } } }
获取此类中三个公共字段(b
、name
和参数化类型 list
)的类型的示例输出如下。用户输入以斜体表示。
$ *java FieldSpy FieldSpy b* Type: class [[Z GenericType: class [[Z $ *java FieldSpy FieldSpy name* Type: class java.lang.String GenericType: class java.lang.String $ *java FieldSpy FieldSpy list* Type: interface java.util.List GenericType: java.util.List<java.lang.Integer> $ *java FieldSpy FieldSpy val* Type: class java.lang.Object GenericType: T
字段 b
的类型是布尔值的二维数组。类型名称的语法在 Class.getName()
中有描述。
字段 val
的类型报告为 java.lang.Object
,因为泛型是通过类型擦除实现的,在编译期间删除了关于泛型类型的所有信息。因此,T
被替换为类型变量的上界,在本例中为 java.lang.Object
。
Field.getGenericType()
如果存在,将查阅类文件中的 Signature 属性。如果该属性不可用,则退而求其次使用 Field.getType()
,这个方法在引入泛型之后并没有改变。反射中其他以 getGeneric*Foo*
命名的方法,对于某个 Foo 值的实现方式类似。
检索和解析字段修饰符
原文:
docs.oracle.com/javase/tutorial/reflect/member/fieldModifiers.html
有几个修饰符可能是字段声明的一部分:
- 访问修饰符:
public
、protected
和private
- 控制运行时行为的字段特定修饰符:
transient
和volatile
- 限制为一个实例的修饰符:
static
- 禁止值修改的修饰符:
final
- 注解
方法Field.getModifiers()
可用于返回表示字段的声明修饰符集合的整数。该整数中表示修饰符的位在java.lang.reflect.Modifier
中定义。
FieldModifierSpy
示例演示了如何搜索具有给定修饰符的字段。它还通过调用Field.isSynthetic()
和Field.isEnumCostant()
确定所定位的字段是合成的(编译器生成的)还是枚举常量。
import java.lang.reflect.Field; import java.lang.reflect.Modifier; import static java.lang.System.out; enum Spy { BLACK , WHITE } public class FieldModifierSpy { volatile int share; int instance; class Inner {} public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); int searchMods = 0x0; for (int i = 1; i < args.length; i++) { searchMods |= modifierFromString(args[i]); } Field[] flds = c.getDeclaredFields(); out.format("Fields in Class '%s' containing modifiers: %s%n", c.getName(), Modifier.toString(searchMods)); boolean found = false; for (Field f : flds) { int foundMods = f.getModifiers(); // Require all of the requested modifiers to be present if ((foundMods & searchMods) == searchMods) { out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n", f.getName(), f.isSynthetic(), f.isEnumConstant()); found = true; } } if (!found) { out.format("No matching fields%n"); } // production code should handle this exception more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } private static int modifierFromString(String s) { int m = 0x0; if ("public".equals(s)) m |= Modifier.PUBLIC; else if ("protected".equals(s)) m |= Modifier.PROTECTED; else if ("private".equals(s)) m |= Modifier.PRIVATE; else if ("static".equals(s)) m |= Modifier.STATIC; else if ("final".equals(s)) m |= Modifier.FINAL; else if ("transient".equals(s)) m |= Modifier.TRANSIENT; else if ("volatile".equals(s)) m |= Modifier.VOLATILE; return m; } }
示例输出如下:
$ *java FieldModifierSpy FieldModifierSpy volatile* Fields in Class 'FieldModifierSpy' containing modifiers: volatile share [ synthetic=false enum_constant=false ] $ *java FieldModifierSpy Spy public* Fields in Class 'Spy' containing modifiers: public BLACK [ synthetic=false enum_constant=true ] WHITE [ synthetic=false enum_constant=true ] $ *java FieldModifierSpy FieldModifierSpy\$Inner final* Fields in Class 'FieldModifierSpy$Inner' containing modifiers: final this$0 [ synthetic=true enum_constant=false ] $ *java FieldModifierSpy Spy private static final* Fields in Class 'Spy' containing modifiers: private static final $VALUES [ synthetic=true enum_constant=false ]
请注意,有些字段即使在原始代码中未声明也会被报告。这是因为编译器会生成一些合成字段,这些字段在运行时是必需的。为了测试一个字段是否是合成的,示例调用Field.isSynthetic()
。合成字段的集合是依赖于编译器的;然而,常用的字段包括用于内部类(即非静态成员类)引用最外层封闭类的this$0
和用于枚举实现隐式定义的静态方法values()
的$VALUES
。合成类成员的名称未指定,可能在所有编译器实现或版本中不同。这些和其他合成字段将包含在Class.getDeclaredFields()
返回的数组中,但不会被Class.getField()
识别,因为合成成员通常不是public
。
因为Field
实现了接口java.lang.reflect.AnnotatedElement
,所以可以使用java.lang.annotation.RetentionPolicy.RUNTIME
来检索任何运行时注解。有关获取注解的示例,请参见检查类修饰符和类型部分。
Java 中文官方教程 2022 版(四十三)(3)https://developer.aliyun.com/article/1488268