Java reflection(反射)

基本翻译自Java SE Tutorial中关于反射的章节:http://docs.oracle.com/javase/tutorial/reflect/index.html

Java的反射机制常被用于检查或者修改在JVM中运行的程序的实时行为,是非常高级的Java特性,如果不是对Java掌握的非常透彻,建议不要随便使用!除此之外,反射是一个非常强大的技术,它可以让你的应用拥有前所未有的能力:

可扩展性特性:使应用可以通过类的名称来创建用户自建的类对象。

类浏览器以及可视化开发环境:类浏览器可以查看类里包含的所有成员。可视化开发环境利用反射提供的类型信息帮助开发人员写出正确的代码

调试器以及测试工具:调试器需要检查类的私有成员。测试工具可利用反射机制系统的调用类中的一系列API,以单元测试保证代码质量(TestNG, JUnit)


反射的缺点:反射会造成性能上的障碍、会带来安全问题、会暴露出代码的内部结构。如果可以用其他方法达到相同的目的,就不应使用反射!


1. 类相关的反射

每个对象要么是一个引用类型要么是一个主类型。所有引用类型都继承自java.lang.Object,类、枚举、数组已经接口都是引用类型,比如java.lang.String,所有主类型的包装类像java.lang.Double,接口java.io.Serializable以及枚举javax.swing.SortOrder。而主类型是固定的几个:boolean, byte, short, int, long, char, float以及double。

JVM会为每个类型的对象实例化一个不可变的java.lang.Class对象(就是说对每个类,JVM中都有一个类型为Class的Object对应这个类),这个对象提供一些方法检测这个对象运行时的属性,比如成员和类型信息。Class类也提供创建新类和对象的功能。最重要的是,Class类(对每个其他类来说是Class对象)是所有反射API的入口。

1.1 获取Class对象

所有反射操作都通过java.lang.Class进行,这个类及其实例化的对象是反射机制的入口。java.lang.reflect包中没有一个类有公共的构建器。要取得这些类,我们需要调用Class类中的某些适当的方法。下面介绍一些获取Class的方法,当然能不能获取还要看代码有没有访问这个对象、类名或者已经存在的Class的权限。

1.1.1 Object.getClass()

如果已经有了对象,最简单的获取其对应Class的方法是调用Object.getClass()方法。当然这个方法只能用于继承自Object的引用类型。

Class h = "foo".getClass(); //返回String对应的Class对象

Class h = System.console().getClass(); //System.console()返回一个Console对象,它调用getClass()方法返回一个java.io.Console对象的Class对象。

enum E { A, B }

Class h = A.getClass(); //返回枚举类型E对应的Class对象

import java.util.HashSet;
import java.util.Set;

Set<String> s = new HashSet<String>();
Class h = s.getClass(); //返回java.util.HashSet类对应的Class对象

1.1.2 通过.class语法获取

如果没有已经实例化的对象,那么也可以通过将'.class'加到这个类型名的尾部来获取对应的Class对象。这是获取主类型Class对象的最简方法。

boolean b;
//Class h = b.getClass(); //无法编译
Class h = boolean.class; //获得boolean对应的Class对象h

Class h = java.io.PrintStream.class; //获取到java.io.PrintStream对应的Class对象

Class h = int[][][].class; //.class 也可用于获取指定类型多维数组的Class对象


1.1.3 Class.forName()

如果我们知道某个类的全名,也可以调用Class类的静态方法Class.forName()来获取该类对应的Class对象。forName()方法不能用于主类型,但是可以用于使用主类型的数组。数组类型名字的语法可参考Class.getName()

Class h = Class.forName("com.hehe.MyClass"); //获取MyClass对应的Class对象

Class hDoubleArray = Class.forName("[D"); //获取类型为主类型double的数组对应的Class对象(与double[].class等同)

Class hStringArray = Class.forName("[[Ljava.lang.String;"); //类型为String的二维数组对应的Class对象,等同于String[][].class


1.1.4 主类型包装类的类型域

除了.class方法获取主类型的Class对象外,还可以通过主类型的包装类来获取。每个主类型已经void类型在java.lang包里都有一个对应的包装类,这些包装类都有一个名叫TYPE的域,这个域对应了被包装的主类型的Class对象。所以我们可以通过主类型的包装类的TYPE域来获取其Class对象。

Class h = Double.TYPE; //获取主类型double对应的Class对象

Class h = Void.TYPE; //获取void类型对应的Class对象


1.1.5 可以返回Class对象的方法

有一些可以获取Class对象的方法,但是其中一些是在这个Class对象本身已经存在的情况下才能调用

Class.getSuperclass() 获取这个Class对象的super类的Class对象。 Class h = javax.swing.JButton.class.getSuperclass(); //Javax.swing.JButton的super类是Javax.swing.AbstractButton

Class.getClasses() 获取该类成员(包括继承自父类的成员)中所有公共的类、接口、枚举类型对应的Class对象,返回的是一个Class数组。Class<?>[] c = Character.class.getClasses(); 

Class.getDeclaredClasses() 获取声明在该类中的成员类、接口以及枚举类型对应的所有Class对象,包括私有成员。

Class.getDeclaringClass()

java.lang.reflect.Field.getDeclaringClass()

java.lang.reflect.Method.getDeclaringClass()

java.lang.reflect.Constructor.getDeclaringClass()

这些方法返回声明了这些成员的类的Class对象。匿名类声明没有这样的包含类,但是却有一个enclosing类。

import java.lang.reflect.Field;
Field f = System.class.getField("out");
Class h = f.getDeclaringClass(); //域out在类System中声明
public class MyClass {
    static Object o = new Object() {
        public void m() {} 
    };
    static Class<c> = o.getClass().getDeclaringClass(); //匿名类对象的declaring class是null
} //匿名类对象有enclosing class,但是没有declaring class
Class.getEnclosingClass() 返回这个类的enclosing class

Class c = Thread.State.class().getEnclosingClass(); //Thread.State域的enclosing class是Thread
public class MyClass {
    static Object o = new Object() { 
        public void m() {} 
    };
    static Class<c> = o.getClass().getEnclosingClass();
} //匿名类对象o的enclosing class是MyClass

1.2 检查类修饰符和类型

一个类可能包含一个或多个修饰符,这些修饰符会影响到类的运行时行为:

  • 访问修饰符:public,protected,private
  • 覆盖需求修饰符:abstract
  • 单一实例修饰符:static
  • 防修改修饰符:final
  • 浮点数行为强制修饰符:strictfp

但是并非所有类都可以使用所有这些修饰符,比如接口不能为final,枚举不能为abstract。修饰符的定义可以在这里找到java.lang.reflect.Modifier。这个包也包含了解码由Class.getModifiers()方法返回的修饰符集合的方法。

下例显示怎样获取类的声明组成,包括修饰符、泛型参数、被实现的接口、继承路径,由于Class类实现了java.lang.reflect.AnnotateElement接口,我们也可以获取运行时annotations。

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;

public class ClassDeclarationSpy {
    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    out.format("Class:%n  %s%n%n", c.getCanonicalName());
	    out.format("Modifiers:%n  %s%n%n",
		       Modifier.toString(c.getModifiers()));

	    out.format("Type Parameters:%n");
	    TypeVariable[] tv = c.getTypeParameters();
	    if (tv.length != 0) {
		out.format("  ");
		for (TypeVariable t : tv)
		    out.format("%s ", t.getName());
		out.format("%n%n");
	    } else {
		out.format("  -- No Type Parameters --%n%n");
	    }

	    out.format("Implemented Interfaces:%n");
	    Type[] intfs = c.getGenericInterfaces();
	    if (intfs.length != 0) {
		for (Type intf : intfs)
		    out.format("  %s%n", intf.toString());
		out.format("%n");
	    } else {
		out.format("  -- No Implemented Interfaces --%n%n");
	    }

	    out.format("Inheritance Path:%n");
	    List<Class> l = new ArrayList<Class>();
	    printAncestor(c, l);
	    if (l.size() != 0) {
		for (Class<?> cl : l)
		    out.format("  %s%n", cl.getCanonicalName());
		out.format("%n");
	    } else {
		out.format("  -- No Super Classes --%n%n");
	    }

	    out.format("Annotations:%n");
	    Annotation[] ann = c.getAnnotations();
	    if (ann.length != 0) {
		for (Annotation a : ann)
		    out.format("  %s%n", a.toString());
		out.format("%n");
	    } else {
		out.format("  -- No Annotations --%n%n");
	    }

        // production code should handle this exception more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }

    private static void printAncestor(Class<?> c, List<Class> l) {
	Class<?> ancestor = c.getSuperclass();
 	if (ancestor != null) {
	    l.add(ancestor);
	    printAncestor(ancestor, l);
 	}
    }
}

output:

$ 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,原因是接口默认就是abstract的,编译器会为每个接口都加上abstract修饰符。这个接口的定义包含两个泛型参数:K和V。这个例子只是打印了这些参数的名字,但是使用java.lang.reflect.TypeVariabble中的方法还可以获取更多信息。

下面是另一个输入的结果,由于数组是运行时对象,所有的类型信息都由JVM定义。特别指出,数组都实现了Cloneable和java.io.Serializable接口,他们的父类总是Object:

$ 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 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 --


$ 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()

1.3 类成员发现

Class中访问类对象的域、方法和构建器的方法可以分为两类:1. 枚举出类中成员和方法的方法; 2. 搜索特点成员的方法。同时,访问直接定义在类中的成员和搜索继承自父接口和父类中的成员的方法也有所不同。下表总结了所有成员定位(就是找到类成员)方法以及他们的特点。

定位域的Class方法:

Class API List of members? Inherited members? Private members?
getDeclaredField() no no yes
getField() no yes no
getDeclaredFields() yes no yes
getFields() yes yes no
定位方法的Class方法:

Class API List of members? Inherited members? Private members?
getDeclaredMethod() no no yes
getMethod() no yes no
getDeclaredMethods() yes no yes
getMethods() yes yes no
定位构建器的Class方法:

Class API List of members? Inherited members? Private members?
getDeclaredConstructor() no N/A1 yes
getConstructor() no N/A1 no
getDeclaredConstructors() yes N/A1 yes
getConstructors() yes N/A1 no
N/A: 构建器不是继承自父类的

实例参见:http://docs.oracle.com/javase/tutorial/reflect/class/classMembers.html


1.4 故障排除

在使用反射时可能会遇到一些问题,下面列出一些典型的错误。

1.4.1 编译警告:

Compiler Warning: "Note: ...uses unchecked or unsafe operations"

当调用方法是,参数类型需要检查或者转换,下面例子中getMethod()会产生一个典型的‘未检查转换警告’(unchecked conversion warning).

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()的参数是参数化类型,这样就会发生unchecked conversion警告。有两种解决方法:

a. 将变量c定义为适当的泛型,此例中应定义如下:

Class<?> c = warn.getClass();

b. 或者使用@SuppressWarnings屏蔽警告

Class c = ClassWarning.class;
@SuppressWarnings("unchecked")
Method m = c.getMethod("m");  
// warning gone

1.4.2 无法访问构建器: InstanitationException

如果一个类的默认构建器对外不可见,当调用Class.newInstance创建该类的实例时就会抛出InstanitationException。

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关键字非常相似,产生此错误的原因也相同。典型的解决方法是利用java.lang.reflect.AccessibleObject类绕过访问控制检查,但是java.lang.Class类没有继承AccessibleObject类,所以此例中的解决方法为修改代码使用Constructor.newInstance(),关于Constructor.newInstance()的实例参考:http://docs.oracle.com/javase/tutorial/reflect/member/ctorTrouble.html,这个例子下面也会讲到。

2. 成员相关反射

反射定义了一个接口java.lang.reflect.Member,这个接口实现了3个接口java.lang.reflect.Field, java.lang.reflect.Method, java.lang.reflect.Constructor。对每一个成员,我们会介绍相关的获取声明和类型信息的API、针对这个成员的特定操作以及一般会遇到的问题。

Note:根据Java SE7语言规范,类成员就是一个类的可继承组件包括域、方法、嵌套类、接口以及枚举类型,由于构建器不是可继承的所以不是成员。这个定义与反射中的成员定义是不同的。

2.1 域

域包含一个类型和一个值,java.lang.reflect.Field类提供了访问给定对象的域的类型信息以及设置/获取域值信息的方法

2.1.1 获取域类型信息

域可以为主类型或者引用类型,主类型是固定的8个:boolean、byte、short、int、long、char、float、double。引用类型是java.lang.Object的子类或者后代类,包括接口、数组以及枚举类型。

下例打印出一个给定的全名类名称和包含其中的域的类型和泛型信息

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();
	}
    }
}
结果输出:

$ 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的类型是一个二维数组,类型名的语法参考:http://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getName--

域val的类型是java.lang.Object,那是因为在编译时所有泛型相关的信息都通过类型清除移除掉了,所以T就会被上一层的类型变量替换,此例中为java.lang.Object


2.1.2 获取和解析域修饰符

以下修饰符可以修饰域:

  • 访问控制修饰符:public、protected、private
  • 域专用运行时行为控制修饰符:transient、volatile
  • 单例修饰符:static
  • 防止修改修饰符:final
  • Annotations

方法Field.getModifiers()可以用于获取代表了声明在该域上的修饰符集合的一个整数,整数的比特位所代表的修饰符定义在类java.lang.reflect.Modifier中

下例展示了怎样搜索使用了给定修饰符的域,通过Field.isSynthetic()和Field.isEnumConstant()方法可以知道该域是synthetic(编译器生成)的还是一个枚举常量。

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 ]
注意有些没有在类中定义的域也被打印出来了,这是因为编译器会生成一些在运行时需要的域,叫做合成域(synthetic fields)。可使用FIeld.isSynthetic()方法检查一个域是否为合成的。根据编译器不同可能生成的合成域也不同,但是常用的有

this$0 用于内部类中引用最外的外部enclosing类

@VALUES 被枚举用于实现隐含的静态方法values()

synthetic fields包含在Class.getDeclaredFields()返回的集合中

Feild类实现了接口java.lang.reflect.AnnotatedElement,通过java.lang.annotation.RetentionPolicy.RUNTIME可以获取运行时annotation,see:http://docs.oracle.com/javase/tutorial/reflect/class/classModifiers.html

2.1.3 获取和设置域的值

我们可以利用反射设置一个类实例中包含的域的值,但是因为这种方法违反了类的设计初衷,一般只在一些特殊情况下比如域值不能通过正常途径设置时才使用,而且应该尽量避免使用。

下例展示了怎样设置类型为long,数组以及枚举的域的值,更多方法参考Field

import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;

enum Tweedle { DEE, DUM }

public class Book {
    public long chapters = 0;
    public String[] characters = { "Alice", "White Rabbit" };
    public Tweedle twin = Tweedle.DEE;

    public static void main(String... args) {
	Book book = new Book();
	String fmt = "%6S:  %-12s = %s%n";

	try {
	    Class<?> c = book.getClass();

	    Field chap = c.getDeclaredField("chapters");
	    out.format(fmt, "before", "chapters", book.chapters);
  	    chap.setLong(book, 12);
	    out.format(fmt, "after", "chapters", chap.getLong(book));

	    Field chars = c.getDeclaredField("characters");
	    out.format(fmt, "before", "characters",
		       Arrays.asList(book.characters));
	    String[] newChars = { "Queen", "King" };
	    chars.set(book, newChars);
	    out.format(fmt, "after", "characters",
		       Arrays.asList(book.characters));

	    Field t = c.getDeclaredField("twin");
	    out.format(fmt, "before", "twin", book.twin);
	    t.set(book, Tweedle.DUM);
	    out.format(fmt, "after", "twin", t.get(book));

        // production code should handle these exceptions more gracefully
	} catch (NoSuchFieldException x) {
	    x.printStackTrace();
	} catch (IllegalAccessException x) {
	    x.printStackTrace();
	}
    }
}
输出结果:

$ java Book
BEFORE:  chapters     = 0
 AFTER:  chapters     = 12
BEFORE:  characters   = [Alice, White Rabbit]
 AFTER:  characters   = [Queen, King]
BEFORE:  twin         = DEE
 AFTER:  twin         = DUM

2.1.4 故障排除

无法转换的类型:IllegalArgumentException
下例中Field.setInt被用于将一个Integer引用类型的域设置为主类型int值,这样就会产生一个IllegalArgumentException。正常赋值而非反射时,对于语句Integer val = 42;编译器会将主类型42转化成引用类型new Integer(42),所以赋值不会有问题。但是反射是实时发生的,没有机制去封装主类型。

import java.lang.reflect.Field;

public class FieldTrouble {
    public Integer val;

    public static void main(String... args) {
	FieldTrouble ft = new FieldTrouble();
	try {
	    Class<?> c = ft.getClass();
	    Field f = c.getDeclaredField("val");
  	    f.setInt(ft, 42);               // IllegalArgumentException

        // production code should handle these exceptions more gracefully
	} catch (NoSuchFieldException x) {
	    x.printStackTrace();
 	} catch (IllegalAccessException x) {
 	    x.printStackTrace();
	}
    }
}
输出结果:

$ java FieldTrouble
Exception in thread "main" java.lang.IllegalArgumentException: Can not set
  java.lang.Object field FieldTrouble.val to (long)42
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
          (UnsafeFieldAccessorImpl.java:146)
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException
          (UnsafeFieldAccessorImpl.java:174)
        at sun.reflect.UnsafeObjectFieldAccessorImpl.setLong
          (UnsafeObjectFieldAccessorImpl.java:102)
        at java.lang.reflect.Field.setLong(Field.java:831)
        at FieldTrouble.main(FieldTrouble.java:11)
解决方法为修改代码手动将主类型封装成引用类型

f.set(ft, new Integer(43));


非公共域:NoSuchFieldException

还是使用上例的代码,如果是访问一个非公共的域就会失败

$ java FieldSpy java.lang.String count
java.lang.NoSuchFieldException: count
        at java.lang.Class.getField(Class.java:1519)
        at FieldSpy.main(FieldSpy.java:12)
应该使用Class.getDeclaredFields()


修改final域:IllegalAccessException

当获取或设置一个私有域(或者其他不能访问的域),或者设置一个final域时(不管它的访问修饰符是什么),都会引发IllegalAccessException

import java.lang.reflect.Field;

public class FieldTroubleToo {
    public final boolean b = true;

    public static void main(String... args) {
	FieldTroubleToo ft = new FieldTroubleToo();
	try {
	    Class<?> c = ft.getClass();
	    Field f = c.getDeclaredField("b");
// 	    f.setAccessible(true);  // solution
	    f.setBoolean(ft, Boolean.FALSE);   // IllegalAccessException

        // production code should handle these exceptions more gracefully
	} catch (NoSuchFieldException x) {
	    x.printStackTrace();
	} catch (IllegalArgumentException x) {
	    x.printStackTrace();
	} catch (IllegalAccessException x) {
	    x.printStackTrace();
	}
    }
}
输出结果:

$ java FieldTroubleToo
java.lang.IllegalAccessException: Can not set final boolean field
  FieldTroubleToo.b to (boolean)false
        at sun.reflect.UnsafeFieldAccessorImpl.
          throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:55)
        at sun.reflect.UnsafeFieldAccessorImpl.
          throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:63)
        at sun.reflect.UnsafeQualifiedBooleanFieldAccessorImpl.setBoolean
          (UnsafeQualifiedBooleanFieldAccessorImpl.java:78)
        at java.lang.reflect.Field.setBoolean(Field.java:686)
        at FieldTroubleToo.main(FieldTroubleToo.java:12)
Field扩展了AccessibleObject可以用来绕过这种检查


2.2 方法

方法有返回值、参数、还有可能抛出异常,java.lang.reflect.Method类提供了一些方法获取这些信息,以及调用一个给定对象中的方法

2.2.1 获取方法类型信息

方法声明部分包括方法名字、修饰符、参数、返回值以及可抛出的异常列表,java.lang.reflect.Method类提供了获取这些信息的方法。

下例展示了怎样枚举一个类中所有的方法,并获取每个方法的返回、参数、异常类型

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import static java.lang.System.out;

public class MethodSpy {
    private static final String  fmt = "%24s: %s%n";

    // for the morbidly curious
    <E extends RuntimeException> void genericThrow() throws E {}

    public static void main(String... args) {
	try {
	    Class<?> c = Class.forName(args[0]);
	    Method[] allMethods = c.getDeclaredMethods();
	    for (Method m : allMethods) {
		if (!m.getName().equals(args[1])) {
		    continue;
		}
		out.format("%s%n", m.toGenericString());

		out.format(fmt, "ReturnType", m.getReturnType());
		out.format(fmt, "GenericReturnType", m.getGenericReturnType());

		Class<?>[] pType  = m.getParameterTypes();
		Type[] gpType = m.getGenericParameterTypes();
		for (int i = 0; i < pType.length; i++) {
		    out.format(fmt,"ParameterType", pType[i]);
		    out.format(fmt,"GenericParameterType", gpType[i]);
		}

		Class<?>[] xType  = m.getExceptionTypes();
		Type[] gxType = m.getGenericExceptionTypes();
		for (int i = 0; i < xType.length; i++) {
		    out.format(fmt,"ExceptionType", xType[i]);
		    out.format(fmt,"GenericExceptionType", gxType[i]);
		}
	    }

        // production code should handle these exceptions more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }
}
输出结果:

$ java MethodSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
  (java.lang.Class<?>[]) throws java.lang.NoSuchMethodException,
  java.lang.SecurityException
              ReturnType: class java.lang.reflect.Constructor
       GenericReturnType: java.lang.reflect.Constructor<T>
           ParameterType: class [Ljava.lang.Class;
    GenericParameterType: java.lang.Class<?>[]
           ExceptionType: class java.lang.NoSuchMethodException
    GenericExceptionType: class java.lang.NoSuchMethodException
           ExceptionType: class java.lang.SecurityException
    GenericExceptionType: class java.lang.SecurityException
方法的声明为:
public Constructor<T> getConstructor(Class<?>... parameterType)
注意方法的返回和参数类型都是泛型,Method.getGenericReturnType()会参考类文件中存在的签名属性,如果不存在,那他就会转而调用Method.getReturnType(),这个方法没有因为泛型的引入而改变,其他形如getGenericFoo()的方法也类似。

再注意最后一个参数(其实是唯一的参数),parameterType,是类型为java.lang.CLass的可变数量的参数,类型用java.lang.Class的一位数组代表。

下例使用了有返回类型为泛型的方法:

$ java MethodSpy java.lang.Class cast
public T java.lang.Class.cast(java.lang.Object)
              ReturnType: class java.lang.Object
       GenericReturnType: T
           ParameterType: class java.lang.Object
    GenericParameterType: class java.lang.Object
泛型的返回类型为class.java.lang.Object,那是因为泛型的类型信息在编译时被类型清除移除了。
下例展示了有多个重载的方法:

$ java MethodSpy java.io.PrintStream format
public java.io.PrintStream java.io.PrintStream.format
  (java.util.Locale,java.lang.String,java.lang.Object[])
              ReturnType: class java.io.PrintStream
       GenericReturnType: class java.io.PrintStream
           ParameterType: class java.util.Locale
    GenericParameterType: class java.util.Locale
           ParameterType: class java.lang.String
    GenericParameterType: class java.lang.String
           ParameterType: class [Ljava.lang.Object;
    GenericParameterType: class [Ljava.lang.Object;
public java.io.PrintStream java.io.PrintStream.format
  (java.lang.String,java.lang.Object[])
              ReturnType: class java.io.PrintStream
       GenericReturnType: class java.io.PrintStream
           ParameterType: class java.lang.String
    GenericParameterType: class java.lang.String
           ParameterType: class [Ljava.lang.Object;
    GenericParameterType: class [Ljava.lang.Object;
重名的重载方法都会包含到Class.getDeclaredMethods()的返回结果中。

2.2.2 获取方法的参数名字

翻译真是累人的活,先到这吧。。。


2.2.3 获取并解析方法的修饰符


2.2.4 调用方法


2.2.5 故障排除



2.3 构建器



3. 数组和枚举类型

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章