工学1号馆

home

java反射实战

Wu Yudong    June 14, 2015     Java   591   

原创文章,转载请注明: 转载自工学1号馆

本文将利用java反射的理论知识进行实战

在之前的文章《java反射的应用》和《java核心系列11-反射》中,进行了java反射的理论与相关实践,本文将就一些常用的属性进行实战

反射的基石Class

Class类代表Java类,它的各个实例对象又分别对应什么呢?

对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型–Class类

如何得到各个字节码对应的实例对象( Class类型)

类名.class,例如,System.class

对象.getClass(),例如,new Date().getClass()

Class.forName(“类名”),例如,Class.forName(“java.util.Date”);

九个预定义Class实例对象:

参看Class.isPrimitive方法的帮助

int.class == Integer.TYPE

数组类型的Class实例对象

Class.isArray()

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…

跑个程序吧:

public static void main(String[] args) throws Exception{
	// TODO Auto-generated method stub
	String  str1 = "wuyudong";
	Class cls1 = str1.getClass();
	Class cls2 = String.class;
	Class cls3 = Class.forName("java.lang.String");
	System.out.println(cls1 == cls2);  //true
	System.out.println(cls1 == cls3);  //true
	System.out.println(int.class.isPrimitive());  //true
	System.out.println(String.class.isPrimitive());  //false
	System.out.println(int.class == Integer.class);  //false
	System.out.println(int.class == Integer.TYPE);  //true
	System.out.println(int[].class.isPrimitive());  //false
}

理解反射的概念

反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量、方法、构造方法、修饰符、包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点

Constructor类

Constructor代表某个类中的一个构造方法

得到某个类所有的构造方法:

Constructor [] constructors= Class.forName("java.lang.String").getConstructors();

得到某一个指定的构造方法:

Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
//获得方法时要用到类型

创建实例对象:

String str = new String(new StringBuffer("abc"));  //通常方式
String str = (String)constructor.newInstance(new StringBuffer("abc")); //反射方式
//调用获得的方法时要用到上面相同类型的实例对象

Class.newInstance()方法:

String obj = (String)Class.forName("java.lang.String").newInstance();

该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

跑个程序:

//使用反射实现:new String(new StringBuffer("wuyudong"));
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);//获取对应的Constructor
String str = (String)constructor1.newInstance(new StringBuffer("wuyudong")); //实例化

Field类

Field代表某个类中的一个成员变量

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。

示例代码:

首先定义一个实验反射的类ReflectPoint:

public class ReflectPoint {
	private int x;
	public int y;
	public String str1 = "bsdbngas";
	public String str2 = "assadbfbb";
	public ReflectPoint(int x, int y) {
		this.x = x;
		this.y = y;
	}
	public String toString() {
		return str1 + ":" + str2;
	}
}

获取实例化对象中的变量的值

public static void main(String[] args) throws Exception{
	// TODO Auto-generated method stub
	Constructor constructor1 = String.class.getConstructor(StringBuffer.class);//获取对应的Constructor
	String str = (String)constructor1.newInstance(new StringBuffer("wuyudong")); //实例化
	System.out.println(str);
		
	ReflectPoint point = new ReflectPoint(1,7);
	Field fieldY = Class.forName("itcast.day1.ReflectPoint").getField("y");
	System.out.println(fieldY.get(point));
	//error! Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");
	Field fieldX = Class.forName("itcast.day1.ReflectPoint").getDeclaredField("x");  //由于x为私有变量
	fieldX.setAccessible(true);	//由于x为私有变量
	System.out.println(fieldX.get(point));
}

实战:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的”b”改成”a”。

public static void printString(Object obj) throws Exception{
	Class cls = obj.getClass();  //获取参数对应的类
	Field[] fields = cls.getFields();  //获取该类所有的字段
	for(Field field : fields) {        //迭代遍历所有的字段
		if(field.getType() == String.class) {  //判断字段的类型是否为String
			String oldstr = (String)field.get(obj);  //获取String域
			String newstr = oldstr.replace('b', 'a');  //替换操作
			field.set(obj, newstr);	  //设置域
		}
	}
}

Method类

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对象对应的是一个静态方法!

用反射方式执行某个类中的main方法

目标:

写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,要明白为什么要用反射方式去调用

问题:

启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

解决办法:

mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"});

编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了

具体代码如下:

class TestArguments {  //首先定义一个实验类
	public static void main(String[] args) {
		for(String arg : args) {
			System.out.println(arg);
		}
	}
}
public class ReflectTest {
	public static void main(String[] args) throws Exception{
	Method mMain = Class.forName(args[0]).getMethod("main", String[].class);
	mMain.invoke(null, new Object[]{new String[]{"aaa","bbb"}});
	mMain.invoke(null,(Object)new String[]{"aaa","bbb"});
}

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)