文档

Java™ 教程-Java Tutorials 中文版
发现类成员
Trail: The Reflection API
Lesson: Classes

发现类成员

Class 中提供了两类方法,用于访问字段,方法和构造函数:枚举这些成员的方法和搜索特定成员的方法。还有一些不同的方法可以访问直接在类上声明的成员,而不搜索超接口和超类的继承成员。下表提供了所有成员定位方法及其特征的摘要。


定位字段的类方法
ClassAPI 成员列表? 继承的成员? 私有成员?
getDeclaredField() no no yes
getField() no yes no
getDeclaredFields() yes no yes
getFields() yes yes no


定位方法的类方法
ClassAPI 成员列表? 继承的成员? 私有成员?
getDeclaredMethod() no no yes
getMethod() no yes no
getDeclaredMethods() yes no yes
getMethods() yes yes no


定位构造函数的类方法
ClassAPI 成员列表? 继承的成员? 私有成员?
getDeclaredConstructor() no N/A1 yes
getConstructor() no N/A1 no
getDeclaredConstructors() yes N/A1 yes
getConstructors() yes N/A1 no

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("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()。此路径的后续部分 Examining Enums 中的 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());
}

Previous page: Examining Class Modifiers and Types
Next page: Troubleshooting