首页 > Java > 高新技术(四) 反射
2014
05-28

高新技术(四) 反射

①反射的基石:Class类

知识点一:Class类 –> 代表一类什么样的事物?

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class类。

Java类用于描述一类事物的共性,该类事物有什么属性、没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。那么这些Java类是否也属于同一类事物,是不是也可以用一个类来描述这类事物呢?答案是可以的,这个类的名字就是Class类。Class类描述了哪些方面的信息呢?类的名字、所属包、访问属性、字段名称列表、方法名称列表等等,学习反射,首先要明白class这个类。

人 –> Person

Java类 –> Class

知识点二:Person类代表人,它的实例对象是张三、李四这样一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?

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

一个类的对象在内存中占用一片空间;一个类的字节码文件(.class)被类加载器加载到内存中,占用一片存储空间,被加载到内存中的字节码也对应着一个对象,这个对象的类型就是Class类。

知识点三:如何得到各个字节码对应的实例对象(Class类型):三种方式

类名.class | 对象.getClass() | Class.forName(“类名”)

其中,第三种方式表示加载类名所对应的类的字节码,并返回该类对应的Class对象。

知识点四:Class中的实例方法isPrimitive(),判断该Class对象是否表示预定义类型。

9种预定义类型的Class对象:包括8个基本类型 + void关键字,判断方法:isPrimitive();

9种预定义类型各对应的一个包装类,每个包装类中有个常量字段TYPE,分别表示该包装类对应基本类型的Class对象,即:int.class == Integer.TYPE为true。

②反射

知识点一:反射的概念

反射就是把一个Java类中的各种成分(字段、方法、构造函数、包等等)映射成相应的Java类(Field、Method、Constructor、Package)。

通过Class类的实例方法可以得到这些实例对象,得到这些实例对象后有什么用?怎么用?这正是学习和应用反射的要点。

知识点二:Constructor类:代表某个类中的一个构造方法。

A1.得到某个类所有的构造方法:Constructor[] constructors = Class.forName(“java.lang.String”).getConstructors();

A2.得到某个指定的构造方法:Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class); //获得Constructor对象时,需要清楚获得哪个构造方法对应的Constructor对象,此时需要传入构造方法对应的参数的类型

B.利用Constructor对象创建某个类实例对象:

String str = (String)constructor.newInstance(new StreamBuffer(“abc”)); //调用获得的构造方法时要用同上面相同类型的实例对象。

上面演示了通过反射的方式调用构造函数创建实例对象的过程:Class — Constructor对象 — newInstance。Class对象也提供了一个newInstance()方法调用无参构造函数直接创建实例对象,过程可表示为:Class — newInstance,如:

String str = (String)Class.forName(“java.lang.String”).newInstance();

知识点三:Field类:代表某个类中的一个成员变量。

第一个例子:利用反射访问某个字段的值:先得到类中的字段(Field对象),再取指定的对象所对应的该字段值。

public class GetFieldTest {
	public int x;
	private int y;

	public GetFieldTest(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	public static void main(String[] args) throws Exception{
		GetFieldTest gft = new GetFieldTest(3,5);
		//访问字段public字段x
		Field fieldX = gft.getClass().getField("x");
		System.out.println(fieldX.get(gft));
		//访问字段private字段y -- 暴力反射
		Field fieldY = gft.getClass().getDeclaredField("y");
		fieldY.setAccessible(true);
		System.out.println(fieldY.get(gft));
	}
}

注:上面的Filed对象(fieldX、fieldY)是对应到类上的成员变量,并不表示某个具体的对象的成员变量的值,此时可以利用Field类中的get()方法返回指定对象上此Field表示的字段的值。

第二个例子:利用反射将任意一个对象中的所有String类型的成员变量内容中的“b”替换成“a”。

public class GetFieldTest2 {
	public String str1 = "baidu";
	public String str2 = "tencent";
	public String str3 = "alibaba";
	public String toString(){
		return str1+"\t"+str2+"\t"+str3;
	}

	public static void main(String[] args) throws Exception{
		GetFieldTest2 gft = new GetFieldTest2();
		System.out.println(gft);
		changeA2B(gft);
		System.out.println(gft);
	}

	public static void changeA2B(Object obj) throws Exception{
		Field[] fields = obj.getClass().getFields();
		for(Field field : fields){
			if(field.getType() == String.class){
				String oldValue = (String)field.get(obj);
				String newValue = oldValue.replace("b", "a");
				field.set(obj, newValue);
			}
		}
	}
}

注:changeA2B方法中的if条件判断中用“==”而不用equals的原因是因为比较的内容是否为内存中同一段字节码,因此只需使用“==”比较对象的内存地址即可。

知识点四、Method类:代表某个类中的一个成员方法

第一个例子:利用反射调用一个普通方法

A. 得到类中的某一个方法:Method charAt = Class.forName(“java.lang.String”).getMethod(“charAt”, int.class);

B. 利用反射调用该方法:System.out.println(charAt.invoke(str, 1));

如果传递给Method对象的invoke()方法的第一个参数为null,则表明该Method对象对应的是一个静态方法。

第二个例子:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main()方法。

思路:如果在编写代码时知道该类,则通过类名.main()的普通方法就可以去调用,但如果该类名由用户指定,则不可以通过普通方式去执行main()方法,可通过反射完成。使用反射的方式调用参数中含有数组的方法时,如何去传递参数是这儿的重点!(将数组参数转型为Object)

public class GetFieldTest3 {
	public static void main(String[] args) throws Exception{
		String className = args[0];
		Method mainMethod = Class.forName(className).getMethod("main",String[].class);
		mainMethod.invoke(null,(Object)new String[]{"aaa","bbb","ccc"});
	}
}

class CallMe{
	public static void main(String[] args){
		for(String arg : args){
			System.out.println(arg);
		}
	}
}

知识点五:数组与Object的关系

A.具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

B.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

C.基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。理解下面程序的输出(asList()方法的参数是Object…obj):

int[] a1 = {1,2};
String[] s1 = {"a","b","c"};
System.out.println(Arrays.asList(a1));//[[I@15db9742]
System.out.println(Arrays.asList(s1));//[a, b, c]

知识点六:数组的反射( java.lang.reflect.Array工具类 )

public class ArrayReflectTest {

	public static void main(String[] args) {
		int[] ia = {1,2,3};
		String[] sa = {"a","b","c"};

		printObject(ia);
		printObject(sa);
		printObject(Arrays.asList(sa));
	}

	private static void printObject(Object obj) {
		Class cls = obj.getClass();
		if(cls.isArray()){
			int length = Array.getLength(obj);
			for(int i = 0;i < length;i++){
				System.out.print(Array.get(obj, i)+"\t");
			}
			System.out.println();
		}else{
			System.out.println(obj);
		}
	}
}

知识点七:反射的作用 — 实现框架功能(框架是别人调用你,工具是你调用别人)

框架是调用用户的类,而在写框架时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象了,而要用反射方式来做。(将类名写入配置文件中,配置文件通过类加载器加载到内存,然后在程序中通过Class.forName( )加载配置文件中的类 )

③内省(introspector):主要对JavaBean操作

知识点一、 JavaBean –> 一种特殊的Java类,主要用于传递数据信息。这种java类中的方法主要用于访问私有字段(set、get方法)。

如果要在两个模块之间传递多个信息,可将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(简称VO)。

一个JavaBean的属性是根据get、set方法的名称来推断出来的。将下面的Person类当做一个JavaBean来看,则它有一个age属性(设置、获取age属性,不是x)。

class Person(){

private int x;

public int getAge(){

return x;

}

public void setAge(int x){

this.x = x;

}

}

注:获取JavaBean的属性名时,如果属性名第二个字母是小的,则把第一个字母变成小的。如:gettime –> time,getCPUInfo –> CPUInfo

知识点二、对JavaBean的简单内省操作 : JDK中提供了对JavaBean操作的一些API(java.beans.*),这套API就称为内省。

一个符合JavaBean特点的类可以当成普通类一样进行使用,但把它当做JavaBean使用会带来一定的方便。下面演示了将一个类当做普通类和JavaBean分别进行获取属性的操作(get方法),提供对象ist、属性age:

①当做普通类操作具体步骤为:age –> getAge –> methodGetAge

②当做JavaBean操作具体步骤为:age — > methodGetAge (使用内省操作)

可以看到使用内省操作减少了从属性名(age)计算方法名(getAge)的步骤。

public class IntroSpectorTest {
	private int x;
	public IntroSpectorTest(int x){
		this.x = x;
	}
	public int getAge(){
		return x;
	}
	public void setAge(int x){
		this.x = x;
	}

	public static void main(String[] args) throws Exception{
		IntroSpectorTest ist = new IntroSpectorTest(24);
		String propertyName = "age";

		System.out.println(getPropertyAsJavaBean(ist, propertyName));

		System.out.println(getPropertyAsNormalJava(ist, propertyName));

	}
	private static Object getPropertyAsNormalJava(Object obj,	String propertyName) throws Exception {
		String methodName = "getAge";//方法名根据提供的属性名age计算得到,这儿的过程省略
		Method methodGetAge2 = obj.getClass().getMethod(methodName);
		Object retVal2 = methodGetAge2.invoke(obj);

		return retVal2;
	}

	private static Object getPropertyAsJavaBean(Object obj,	String propertyName) throws Exception {

		PropertyDescriptor pd = new PropertyDescriptor(propertyName, obj.getClass());
		Method methodGetAge = pd.getReadMethod();
		Object retVal = methodGetAge.invoke(obj);

		return retVal;
	}

}

知识点三、 对JavaBean的复杂内省操作

下面的代码改写了上面例子中的getPropertyAsJavaBean()方法,采用遍历BeanInfo的所有属性方式来查找和设置obj对象的propertyName属性。【在程序中把一个类当做JavaBean来看,就是调用IntroSpector.getBeanInfo()方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息。】

private static Object getPropertyAsJavaBean(Object obj,	String propertyName) throws Exception {
		Object retVal = null;
		BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
		PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
		for(PropertyDescriptor pd : pds){
			if(pd.getName().equals(propertyName)){
				Method methodGetAge = pd.getReadMethod();
				retVal = methodGetAge.invoke(obj);
				break;
			}
		}
		return retVal;
	}

 

④ 使用BeanUtils工具包操作JavaBean

待补充。


留下一个回复

你的email不会被公开。