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 构造函数不会被继承。
给定一个类名和感兴趣的成员指示, 示例使用ClassSpyget*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());
}