反射就是动态加载一个指定的类,获取该类中所有的内容,并将字节码文件及其中的内容都封装成对象,以便于操作这些成员。反射的主要使用对象时工具的构造者,而不是应用程序员。反射的好处就在于增强了程序的扩展性。
反射的基本步骤:1,获得Class对象,就是获取到指定名称的字节码文件对象。2,实例化对象,获得类的属性、方法或构造函数。3,访问属性,调用方法,调用构造函数创建对象。而获取字节码文件Class对象的方法有三种:1,对象.getClass(),弊端是先要有该类对象才能调用指定方法。2,类名.class,尽管不再需要对象,但是仍然需要先明确该类。3,Class.forName("完整的类名"),该方法是扩展性最好的,指定什么类名,就获取什么类字节码文件对象。
下面来看下获取字节码文件Class对象的三种方法的示例代码:
package cn.itcast.reflect.p1.demo;
import cn.itcast.bean.Person;
public class ReflectDemo {
/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
getClassObj_3();
}
public static void getClassObj_3() throws ClassNotFoundException {
Class clazz = Class.forName("cn.itcast.bean.Person");
System.out.println(clazz.getName());
}
public static void getClassObj_2() {
//每一种数据类型都有自己的描述。想要获取该类型的字节码文件对象。直接通过一个静态的属性就可以完成。
Class clazz = Person.class;
System.out.println(clazz.getName());
}
public static void getClassObj_1(){
//使用对象的getClass方法。
Person p = new Person();
Class clazz1 = p.getClass();
// System.out.println(clazz1.getName());
Person p2 = new Person();
Class clazz2 = p2.getClass();
System.out.println(clazz1 == clazz2);
}
}
Constructor类代表某一个类中的构造方法。
得到某一个构造方法,例子:
Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
得到某个类所有的构造方法,例子:
Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
创建实例对象:
通常方式:String str = new String(newStringBuffer("abc"));
反射方式:String str = (String)constructor.newInstance(newStringBuffer("abc"));
Class.newInstance()方法,例子:
String obj = (String)Class.forName("java.lang.String").newInstance();
该方法先得到默认的构造方法,然后用构造方法。
Filed类代表某个类中的一个成员变量。先来看一段代码:
ReflectPoint rp = new ReflectPoint(3,5);
Class clazz = rp.getClass();
Field fieldX = clazz.getField("x");
System.out.println(fieldX.get(rp));
Field fieldY = clazz.getDeclaredField("y");
fieldY.setAccessible(true);
System.out.println(fieldY.get(rp));
需要说明的是在自定义类ReflectPoint中,x为public,而y则是private。上述代码体现了暴力反射,由于y是一个私有域,因此需要使用getDeclaredField()方法来获取域,并用setAccessible(true)方法使域中的值允许被获取。再来看一个实例应用:
private static void changeStringValue(Object obj) throws Exception
{
//将传进来的类的成员全部获取出来。
Field[] fields = obj.getClass().getFields();
//判断要获取的类的成员的类型有没有String类型的。
for (Field field : fields)
{
//这里我们用==这个符号合适,
//用equals方法比较不合适,如是大家都是string类型的话那比起来就没有意义。
if(field.getType()==String.class)
{
//若是有的话就将字段获取出来。
String oldValue = (String)field.get(obj);
//通过String类中的replace方法将我们要替换的值替换掉。
String newValue = oldValue.replace('a','b');
//修改好之后便将新的值写会到对象中。
field.set(obj,newValue);
}
}
}
上述代码实现了对字符串中内容改变的功能,当然只是简单的将“a”替换成“b”。
Method类代表某个类中的一个成员方法。
得到类中的某一个方法,例子:
Method charAt =Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式:System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,则说明该Method对象对应的是一个静态方法。另外,由于在JDK1.5之前,java并未引入可变参数,因此JDk1.4和JDK1.5的invoke()方法是有区别的:
Jdk1.5:public Object invoke(Objectobj,Object... args)
Jdk1.4:public Object invoke(Objectobj,Object[] args)
因此,如果按JDK1.4的语法,需要将一个数组作为参数传递给invoke()方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以调用charAt()方法的代码也可以用JDK1.4改写为charAt.invoke(str,new Object[]{1})形式。
用反射方式执行某一个类中的main方法,这一情况有需要注意的地方,先来看代码:
string startingClassName=args[0]; //在MyEclipse中设置传入参数为完整的类名。
Method mainMethod = Class.forName(startingClassName).getMethod("main",String[].class);//反射出main类。
mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
我们都知道,启动java程序的main方法的参数是一个字符串数组,此外,新版JDK必然需要兼容JDK1.4的语法,参考前文刚刚讲到过的一个数组作为参数传递给invoke()方法时,JDK1.4不同的处理方式,可以得出结论,在给main方法传递参数时不能使用使用代码mainMethod.invoke(null,new String[]{"xxx"})。解决办法正如所示代码中的那样,编译器会做特殊处理,编译时不把参数当做数组看待,因此也就不会将数组拆包成若干个参数。另外则是写成mainMethod.invoke(null,new Object[]{new String[]{"xxx"}}),该方法则是既然按JDK1.4的语法会拆包后取参数,为了将整个数组做为参数,那就给这个数组打个包让javac去拆。
既然谈到了数组的情况,那就再来看看数组的反射。先看示例代码:
private static void printObject(Object obj) {
Class clazz=obj.getClass();
if(clazz.isArray())
{
int len=Array.getLength(obj);
for(int i=0;i<len;i++)
{
System.out.println(Array.get(obj,i));
}
}else{
System.out.println(obj);
}
}
总结一下数组反射需要注意的地方。1,具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class对象。2,代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。3,基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,也可以当做Object[]类型使用。
427

被折叠的 条评论
为什么被折叠?



