Class类

java中所有的类是一类事物,我们用一个名为Class的类来描述这类事物。

获取Class类对象的三种方法:

类名.class
	new 类名().getClass()
	Class.forName("类的全称") // 类的全称指包名.类名

第三种方式,可以用一个字符串变量代替括号里的字符串。在程序运行时,从配置文件里加载字符串变量的值,所以可以事先不知道类名。但前两种方式必须要先知道类名才行。

java中的9中数据类型(boolean, byte, char, short, int, long, float, double, void)都有对应的Class对象。


反射的概念

java的类,这类事物有很多属性,比如包名、方法、成员变量等等。每一个属性又是一类事物,所以把每一种属性又定义为一个类。这就是java中反射的概念。

构造函数的反射:

得到所有构造方法实例

//得到所有类的构造方法对象
	Constructor[] conss = Class.forName("java.lang.String").getConstructors();

得到一个构造方法

//获取String类的传入参数为StringBuffer对象的构造方法对象
	Constructor cons = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

创建实例对象

//普通方式
	String s1 = new String(new StringBuffer("abc"));
	System.out.println(s1);
	//反射方式由构造函数对象得到原类的实例对象
	String s2 = (String)cons.newInstance(new StringBuffer("abc"));
	System.out.println(s2);

调用空参构造方法创建实例的反射方式

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

与下面的方式是一样的

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

分析:上面这些例子里之所以需要String类型强制转换,是因为在编译阶段,虚拟机是不知道赋值号右边是String类型的,因为类名是通过字符串方式获得的。



成员变量的反射:

先定义一个类,在这个类里定义两个成员变量,然后我们用成员变量的反射来取这两个成员变量。

public class ReflectPoint {
	private int x;
	public int y;
	public ReflectPoint(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}
}



下面我们用成员变量的反射,先来取public的变量y

//创建ReflectPoint对象,给x,y传值
		ReflectPoint rp = new ReflectPoint(3,5);
		//获取成员变量y的反射
		Field fy = rp.getClass().getField("y");
		//通过反射获得对象rp对应的y值
		int y = fy.getInt(rp);
		//输出rp对应的y值
		System.out.println(y);



接下来,我们看private的变量x

首先,如果我们像取y的反射一样来获得x的反射,会发生NoSuchFieldException异常,因为x是私有的。

Field fx = rp.getClass().getField("x");

我们通过下面的方法获得x的反射


Field fx = rp.getClass().getDeclaredField("x");

然后我们想用类似于y的方法获得rp对象对应的x值

int x = fx.getInt(rp);

这时又出现了新的异常IllegalAccessException。通过上面的方法我们能够获得x的反射,但是无法获得私有变量的值。

我们能够通过一种称为暴力反射的方式获得私有成员变量x的值

fx.setAccessible(true);
int x = fx.getInt(rp);



下面的例子是成员变量反射的一个应用:

需求:获得一个类的所有String类型成员变量,并把其中含有的‘b’修改成‘a’

过程:

我们先定义一个类

public class ReflectPoint {
	private int x = 3;
	private int y = 5;
	public String str1 = "baba";
	public String str2 = "book";
	public String str3 = "element";
}



然后通过成员变量的反射实现修改。其中有些函数的异常没有处理,请先在主函数抛出。



//创建ReflectPoint对象
		ReflectPoint rp = new ReflectPoint();
		//获取ReflectPoint的所有成员变量
		Field[] fields = rp.getClass().getFields();
		//遍历所有成员变量
		for(Field field: fields){
			//判断String类型,由于String类型字节码文件只有一个,所以比较用==
			if(field.getType() == String.class){
				//获取rp对象对应的成员变量值
				String oldValue = (String)field.get(rp);
				//替换字母
				String newValue = oldValue.replace('b','a');
				//将新值写入rp对象中
				field.set(rp, newValue);
			}
		}
		//打印修改后的字符串
		System.out.println(rp.str1);
		System.out.println(rp.str2);
		System.out.println(rp.str3);



成员方法的反射:

得到类中的一个方法:

Method methodCharAt = String.class.getMethod("charAt", int.class);

方法调用这个动作是方法自身的属性。所以用下面的方式来调用指定对象的方法,并打印,结果为b

System.out.println(methodCharAt.invoke("abcd", 1));



如果第一个参数是null,代表这是个静态方法。

System.out.println(methodCharAt.invoke(null, 1));


数组的反射:

只要数组元素类型相同而且维度相同,那么它们的字节码文件就是同一个。

数组的字节码文件类的父类是Object类。

注意:基本类型数组,例如int[ ],不能自动转化为Object[ ]。因为int类型不是Object类型,但int[ ] 类型是Object类型,所以int[]会被当成一个Object进行处理。

例如:

1. 很多函数的传入参数是Object[ ]类型,当我们传入一个int[ ]是不行的,无法转化。

2. 很多函数在jdk1.4里面的传入参数是Object[ ]类型,但在jdk1.5中改成了可变参数Object...类型。但jdk向下兼容,当我们传入一个String[ ]时,虚拟机会按照jdk1.4的方式进行处理,但是当我们传入参数类型为int[ ]时,虚拟机不会认为它是Object[ ]类型,所以会按照jdk1.5的传入方式,把int[ ]当成一个Object变量传入函数。


数组反射的应用例子:

需求:打印Object对象的内容,如果是个数组就打印数组内容,如果是单个对象,就打印单个对象内容。

如果我们用下面的方法:

public static void printObject(Object obj){
		System.out.println(obj.toString());
	}

调用上面的函数

String[] a2 = new String[]{"a","b","c"};
		String s = "abc";
		printObject(a2);
		printObject(s);

结果是:

[Ljava.lang.String;@6d9dd520
abc

当我们往里面传入数组时,打印出来的是这个数组的类型以及它的哈希值。这不是我们希望的结果。

下面我们用数组反射Array来实现

public static void printObject(Object obj){
		Class cls = obj.getClass();
		if(cls.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.toString());
		}
	}

调用上面的函数:

String[] a2 = new String[]{"a","b","c"};
		String s = "abc";
		printObject(a2);
		printObject(s);



打印结果:

a

b

c

abc


反射的作用:

用来构建框架。框架是提前写好的,当我再用这个框架时,是框架调用我写的类,也就是说,在我们写类之前,框架就可以写调用程序,这时框架不知道类名,肯定无法用new关键字创建对象,这时候就体现了反射的作用。