Java教程是为JDK 8编写的。本页面描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及后续版本中更新的语言功能的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能和已删除或不推荐选项的信息,请参阅JDK发行说明。
在Class
中提供了两种访问字段、方法和构造函数的方法类别:枚举这些成员的方法和搜索特定成员的方法。此外,还有用于访问类直接声明的成员的方法,以及用于搜索超接口和超类继承的成员的方法。以下表格提供了所有成员定位方法及其特点的摘要。
Class API |
成员列表? | 继承成员? | 私有成员? |
---|---|---|---|
getDeclaredField() |
否 | 否 | 是 |
getField() |
否 | 是 | 否 |
getDeclaredFields() |
是 | 否 | 是 |
getFields() |
是 | 是 | 否 |
Class API |
成员列表? | 继承成员? | 私有成员? |
---|---|---|---|
getDeclaredMethod() |
否 | 否 | 是 |
getMethod() |
否 | 是 | 否 |
getDeclaredMethods() |
是 | 否 | 是 |
getMethods() |
是 | 是 | 否 |
Class API |
成员列表? | 继承成员? | 私有成员? |
---|---|---|---|
getDeclaredConstructor() |
否 | N/A1 | 是 |
getConstructor() |
否 | N/A1 | 否 |
getDeclaredConstructors() |
是 | N/A1 | 是 |
getConstructors() |
是 | N/A1 | 否 |
1 构造函数不会被继承。
给定一个类名和感兴趣的成员指示,
示例使用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("类:%n %s%n%n", c.getCanonicalName()); Package p = c.getPackage(); out.format("包:%n %s%n%n", (p != null ? p.getName() : "-- 没有包 --")); for (int i = 1; i < args.length; i++) { switch (ClassMember.valueOf(args[i])) { case CONSTRUCTOR: printMembers(c.getConstructors(), "构造函数"); break; case FIELD: printMembers(c.getFields(), "字段"); break; case METHOD: printMembers(c.getMethods(), "方法"); break; case CLASS: printClasses(c); break; case ALL: printMembers(c.getConstructors(), "构造函数"); printMembers(c.getFields(), "字段"); printMembers(c.getMethods(), "方法"); printClasses(c); break; default: assert false; } } // 正式代码应该更优雅地处理这些异常 } 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(" -- 没有%s --%n", s); out.format("%n"); } private static void printClasses(Class<?> c) { out.format("类:%n"); Class<?>[] clss = c.getClasses(); for (Class<?> cls : clss) out.format(" %s%n", cls.getCanonicalName()); if (clss.length == 0) out.format(" -- 没有成员接口、类或枚举 --%n"); out.format("%n"); } }
这个示例相对比较简洁;然而,由于java.lang.reflect.Member
接口自从反射的最早实现以来就存在,无法在引入泛型时修改以包含更有用的getGenericString()
方法,所以printMembers()
方法有些笨拙。替代的方法是按照示例中所示进行测试和转换,用printConstructors()
、printFields()
和printMethods()
替换这个方法,或者满足于Member.getName()
相对简洁的结果。
输出示例及其解释如下。用户输入以斜体显示。
$ java ClassSpy java.lang.ClassCastException CONSTRUCTOR 类: java.lang.ClassCastException 包: java.lang 构造函数: public java.lang.ClassCastException() public java.lang.ClassCastException(java.lang.String)
由于构造函数不会被继承,因此在直接超类RuntimeException
和其他超类中定义的异常链接机制构造函数(带有Throwable
参数)将找不到。
$ java ClassSpy java.nio.channels.ReadableByteChannel METHOD 类: java.nio.channels.ReadableByteChannel 包: java.nio.channels 方法: 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 类: ClassMember 包: -- 无包 -- 字段: 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 方法: 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() throws 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()); }