目录
- 简介
- 一个简单的例子
- 设置使用反射
- 模拟instanceof运算
- 了解类的方法
- 获取有关构造函数的信息
- 查找类字段
- 按名称调用方法
- 创建新对象
- 更改字段的值
- 使用数组
- 总结
简介
反射是Java编程语言中的一个特性。它允许执行的Java程序检查或 操作 自身,并操作程序的内部属性。例如,Java类可以获取其所有成员的名称并显示它们。
从程序内部检查和操作Java类的能力听起来可能不太显示,但是在其他编程语言中,这个特性根本不存在。例如,在C或C ++ 程序中无法获取有关该程序中定义的函数的信息。
反射的一个具体用途是在JavaBeans中,软件组件可以通过一个构建工具进行可视化操作。该工具使用反射来获取Java组件 (类) 动态加载时的属性。
一个简单的例子
要了解反射是如何工作的,请考虑以下简单示例:
import java.lang.reflect.*;
public class DumpMethods {
public static void main(String args[])
{
try {
Class c = Class.forName(args[]);
Method m[] = c.getDeclaredMethods();
for (int i =; i < m.length; i++)
System.out.println(m[i].toString());
}
catch (Throwable e) {
System.err.println(e);
}
}
}
对于以下项的调用:
java DumpMethods java.util.Stack
输出为:
public java.lang.Object java.util.Stack.push(
java.lang.Object)
public synchronized
java.lang.Object java.util.Stack.pop()
public synchronized
java.lang.Object java.util.Stack.peek()
public boolean java.util.Stack.empty()
public synchronized
int java.util.Stack.search(java.lang.Object)
也就是说,类的方法名称java.util.Stack列出了它们及其完全限定的参数和返回类型。
此程序使用加载指定的类 class.forName, 然后调用 getDeclaredMethods 方法检索类中定义的方法列表. java.lang.reflect.Method是表示单个类方法的类。
设置使用反射
反射类,例如Method,在java.lang.反射中找到。使用这些类必须遵循三个步骤。第一步是获得一个java.lang.Class要操作的类的对象。java.lang.Class用于表示正在运行的Java程序中的类和接口。
获取类对象的一种方法是:
Class c = Class.forName("java.lang.String");
上述代码获取的类对象 String.
另一种方法是使用:
Class c = int.class;
或者
Class c = Integer.TYPE;
获取基本类型的类信息。后一种方法访问预定义TYPE 包装类型 (例如Integer) 为基本类型。
第二步是调用方法,例如getDeclaredMethods,以获取该类声明的所有方法的列表。
一旦掌握了这些信息,那么第三步就是使用反射API来操作这些信息。例如:
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[].toString());
在下面的示例中,将三个步骤结合在一起,以呈现如何使用反射处理特定应用的独立插图。
模拟instanceof运算
一旦掌握了类信息,下一步通常是询问有关类对象的基本问题。
例如,Class.isInstance方法可以用来模拟instanceof 运算:
class A {}
public class instance {
public static void main(String args[])
{
try {
Class cls = Class.forName("A");
boolean b
= cls.isInstance(new Integer());
System.out.println(b);
boolean b = cls.isInstance(new A());
System.out.println(b);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
在此示例中,类对象A被创建,然后我们检查类实例对象,以查看它们是否是A。Integer(37)不是,但是new A()是。
了解类的方法
反射最有价值和最基本的用途之一是找出类中定义了哪些方法。为此,可以使用以下代码:
import java.lang.reflect.*;
public class method {
private int f(
Object p, int x) throws NullPointerException
{
if (p == null)
throw new NullPointerException();
return x;
}
public static void main(String args[])
{
try {
Class cls = Class.forName("method");
Method methlist[]
= cls.getDeclaredMethods();
for (int i =; i < methlist.length;
i++) {
Method m = methlist[i];
System.out.println("name
= " + m.getName());
System.out.println("decl class = " +
m.getDeclaringClass());
Class pvec[] = m.getParameterTypes();
for (int j =; j < pvec.length; j++)
System.out.println("
param #" + j + " " + pvec[j]);
Class evec[] = m.getExceptionTypes();
for (int j =; j < evec.length; j++)
System.out.println("exc #" + j
+ " " + evec[j]);
System.out.println("return type = " +
m.getReturnType());
System.out.println("-----");
}
}
catch (Throwable e) {
System.err.println(e);
}
}
}
程序首先获取method1的类描述,然后调用getDeclaredMethods(一个用于获取类中定义的每个方法的函数)检索Method 对象列表。这些方法包括public、protect、package和priva。如果你在程序中使用getMethods 而不是getDeclaredMethods,还可以获取继承方法的信息。
程序的输出为:
name = f
decl class = class method
param # class java.lang.Object
param # int
exc # class java.lang.NullPointerException
return type = int
-----
name = main
decl class = class method
param # class [Ljava.lang.String;
return type = void
-----
获取有关构造函数的信息
使用类似的方法来找出类的构造函数。例如:
import java.lang.reflect.*;
public class constructor {
public constructor()
{
}
protected constructor(int i, double d)
{
}
public static void main(String args[])
{
try {
Class cls = Class.forName("constructor");
Constructor ctorlist[]
= cls.getDeclaredConstructors();
for (int i =; i < ctorlist.length; i++) {
Constructor ct = ctorlist[i];
System.out.println("name
= " + ct.getName());
System.out.println("decl class = " +
ct.getDeclaringClass());
Class pvec[] = ct.getParameterTypes();
for (int j =; j < pvec.length; j++)
System.out.println("param #"
+ j + " " + pvec[j]);
Class evec[] = ct.getExceptionTypes();
for (int j =; j < evec.length; j++)
System.out.println(
"exc #" + j + " " + evec[j]);
System.out.println("-----");
}
}
catch (Throwable e) {
System.err.println(e);
}
}
}
在此示例中没有检索到返回类型信息,因为构造函数实际上没有真正的返回类型。
运行此程序时,输出为:
name = constructor
decl class = class constructor
-----
name = constructor
decl class = class constructor
param # int
param # double
-----
查找类字段
还可以找出类中定义了哪些数据字段。为此,可以使用以下代码:
import java.lang.reflect.*;
public class field {
private double d;
public static final int i =;
String s = "testing";
public static void main(String args[])
{
try {
Class cls = Class.forName("field");
Field fieldlist[]
= cls.getDeclaredFields();
for (int i
=; i < fieldlist.length; i++) {
Field fld = fieldlist[i];
System.out.println("name
= " + fld.getName());
System.out.println("decl class = " +
fld.getDeclaringClass());
System.out.println("type
= " + fld.getType());
int mod = fld.getModifiers();
System.out.println("modifiers = " +
Modifier.toString(mod));
System.out.println("-----");
}
}
catch (Throwable e) {
System.err.println(e);
}
}
}
此示例与前面的示例相似。一个新功能是使用Modifier。这是一个反射类,表示在字段成员上找到的修饰符,例如private int。修饰符本身由整数表示,并且Modifier.toString用于返回默认声明顺序中的字符串表示形式 (例如final之前的static)。程序的输出为:
name = d
decl class = class field
type = double
modifiers = private
-----
name = i
decl class = class field
type = int
modifiers = public static final
-----
name = s
decl class = class field
type = class java.lang.String
modifiers =
-----
与方法一样,可以仅获取有关类中声明的字段的信息 (getDeclaredFields),或获取有关超类中定义的字段的信息 (getFields)。
按名称调用方法
到目前为止,已经提出的例子都与获取class有关。但是也可以以其他方式使用反射,例如调用指定名称的方法。
要了解其工作原理,请考虑以下示例:
import java.lang.reflect.*;
public class method {
public int add(int a, int b)
{
return a + b;
}
public static void main(String args[])
{
try {
Class cls = Class.forName("method");
Class partypes[] = new Class[];
partypes[] = Integer.TYPE;
partypes[] = Integer.TYPE;
Method meth = cls.getMethod(
"add", partypes);
method methobj = new method2();
Object arglist[] = new Object[];
arglist[] = new Integer(37);
arglist[] = new Integer(47);
Object retobj
= meth.invoke(methobj, arglist);
Integer retval = (Integer)retobj;
System.out.println(retval.intValue());
}
catch (Throwable e) {
System.err.println(e);
}
}
}
假设一个程序想要调用add方法,但直到执行时才知道。也就是说,在执行期间指定方法的名称 (例如,这可以由JavaBeans开发环境完成)。上面的程序展示了一种方法。
getMethod用于在类中查找具有两个integer参数类型并具有适当名称的方法。一旦找到此方法并将其捕获到Method 对象,它是在适当类型的对象实例上调用的。要调用方法,必须构造一个参数列表,基本整数值为37和47Integer 对象。返回值 (84) 也被包含在Integer 对象。
创建新对象
构造函数不等同于方法调用,因为调用构造函数等同于创建新对象 (最准确地说,创建新对象涉及内存分配和对象构造)。所以最接近前面例子的是:
import java.lang.reflect.*;
public class constructor {
public constructor()
{
}
public constructor(int a, int b)
{
System.out.println(
"a = " + a + " b = " + b);
}
public static void main(String args[])
{
try {
Class cls = Class.forName("constructor");
Class partypes[] = new Class[];
partypes[] = Integer.TYPE;
partypes[] = Integer.TYPE;
Constructor ct
= cls.getConstructor(partypes);
Object arglist[] = new Object[];
arglist[] = new Integer(37);
arglist[] = new Integer(47);
Object retobj = ct.newInstance(arglist);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
它查找处理指定参数类型并调用它的构造函数,以创建对象的新实例。这种方法的价值在于它纯粹是动态的,在执行时而不是在编译时使用构造函数查找和调用。
更改字段的值
反射的另一个用途是改变对象中数据字段的值。它的值再次从反射的动态性质中导出,其中可以在执行程序中按名称查找字段,然后更改其值。以下示例说明了这一点:
import java.lang.reflect.*;
public class field {
public double d;
public static void main(String args[])
{
try {
Class cls = Class.forName("field");
Field fld = cls.getField("d");
field f2obj = new field2();
System.out.println("d = " + fobj.d);
fld.setDouble(fobj, 12.34);
System.out.println("d = " + fobj.d);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
在此示例中,d字段的值设置为12.34。
使用数组
反射的一个用途是创建和操作数组。Java语言中的数组是类的一种特殊类型,并且可以将数组引用分配给Object。
要查看数组的工作方式,请考虑以下示例:
import java.lang.reflect.*;
public class array {
public static void main(String args[])
{
try {
Class cls = Class.forName(
"java.lang.String");
Object arr = Array.newInstance(cls,);
Array.set(arr,, "this is a test");
String s = (String)Array.get(arr,);
System.out.println(s);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
此示例创建一个10长的字符串数组,然后将数组中的位置5设置为字符串值。将检索并显示该值。
以下代码说明了对数组的更复杂的操作:
import java.lang.reflect.*;
public class array {
public static void main(String args[])
{
int dims[] = new int[]{, 10, 15};
Object arr
= Array.newInstance(Integer.TYPE, dims);
Object arrobj = Array.get(arr,);
Class cls =
arrobj.getClass().getComponentType();
System.out.println(cls);
arrobj = Array.get(arrobj,);
Array.setInt(arrobj,, 37);
int arrcast[][][] = (int[][][])arr;
System.out.println(arrcast[][5][10]);
}
}
此示例创建一个5x10x15的int数组,然后继续将数组中的位置 [3][5][10] 设置为值37。请注意,多维数组实际上是数组数组,因此,例如,在第一个array.get之后,arrobj中的结果是10x15数组。再次将其剥离以获得15长的数组,并使用Array.setInt。
请注意,创建的数组类型是动态的,不必在编译时知道。
总结
Java反射非常有用,因为它支持按名称动态检索有关类和数据结构的信息,并允许在执行的Java程序中进行操作。此功能非常强大,但是也要谨慎使用