Java中非常有特点的一个机制就是「反射」。反射可以理解为与正射相对立,正射就是我们知道一个类的作用,通过实例化一个实例对象,直接调用其方法。
例如定义如下一个Orange类:
public class Orange {
private int price;
public String name;
public void setPrice(int price)
{
this.price = price;
}
public int getPrice()
{
return this.price;
}
}
我们已经知道这个类有一个私有域price,以及两个方法。正射的过程如下:
Orange o1 = new Orange();
o1.setPrice(5);
System.out.println(o1.getPrice());
正射就是直接实例化已知类的对象,通过对象调用类的方法。它在编译的时候就已经知道属于什么类,而反射在编译阶段并不会知道要初始化的类对象是什么,因此无法通过new来创造实例,而是在运行阶段,通过反射机制来了解到要操作的类是什么,并获得类的构造,以及调用类的方法。
具体如下面的代码:
try
{
Class clz = Class.forName("pack2.Orange");
Constructor orangeConstructor = clz.getConstructor();
Orange o2 = (Orange)orangeConstructor.newInstance();
Method setPriceMethod = clz.getMethod("setPrice", int.class);
setPriceMethod.invoke(o2, 10);
System.out.println(o2.getPrice());
}
catch (Exception e)
{
e.printStackTrace();
}
下面对这些代码进行详细解析。
Class类
在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的类型标识,这个信息跟踪者每个对象所属的类,保存这些信息的类称为Class。
有如下三种方式得到Class对象:
1.使用静态方法Class.forName(),在知道类的路径时就可以使用:
Class clz = Class.forName("pack2.Orange");
2.使用.class方法,只适用于编译时就知道要操作的class:
Class clz2 = String.class;
3.使用类对象的getClass()方法:
String str1 = new String("abc");
Class clz2 = str1.getClass();
创造Class对象
有两种方式创造Class实例对象:
1.通过Class对象的newInstance()方法,这种方式只能调用无参的构造方法;
Class clz = Class.forName("pack2.Orange");
Orange o3 = (Orange)clz.newInstance();
2.通过Constructor对象的newInstance()方法,这种方式可以任意指定实例化时的构造方法:(代码中还是无参构造方法,可以自己改写下)
Class clz = Class.forName("pack2.Orange");
Constructor orangeConstructor = clz.getConstructor();
Orange o2 = (Orange)orangeConstructor.newInstance();
Field,Method与Constructor
Field,Method和Constructor都可以利用反射分析类的能力,分别描述类的域,类的方法和类的构造器。这三个类都有一个getName方法,用于返回项目的名称(编译时类型,有别于运行时类型)。
Field
Field[] fields = clz.getFields();
for(Field field: fields)
{
System.out.println(field);
System.out.println(field.getName());
}
输出为:
public java.lang.String pack2.Orange.name
name
可见Class对象的getFields方法只能得到非私有域,如果想要得到私有变量,需要使用getDeclaredFields
Method和Constructor也是类似的,在此不重复介绍了。
invoke函数
值得注意的是,Method类中有一个invoke成员方法,可以利用invoke方法调用一个对象所描述的方法:
setPriceMethod.invoke(o2, 10);
括号里的参数第一个为类对象,后面的参数长度是可变的,与Method对象所描述的方法的参数对应。