文档

Java™ 教程
隐藏目录
发现类成员
导航: 反射API
课程:

发现类成员

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());
}

上一页:检查类修饰符和类型
下一页:故障排除