这一篇blog日志就主要记录一下张老师讲到的案例和自己遇到的案例,这样才会对java反射机制理解的更加深透。

(一)用反射方式执行某个类中的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"});//编译器会做特殊处理,
    编译时不把参数当做数组看待,也就不会把数组打散成若干个参数了。我给你的数组,你当然不会当作参数,而是把其中的内容当作参数。

import java.lang.reflect.Method;
public class ReflectTest {

	/**
	 * @param args
	 * 用反射调用其他类的main方法。
	 * 1.用一个字符串变量记录MainReflect类中main方法中string数组的一个字符串。使用这个字符串接收本类的类名。
	 * 2.运用反射技术找到这个main方法
	 * 3.调用invoke()方法向main方法传参数。
	 * 4.在Run Configurations-->Arguments里配置上MainReflect类的包名。
	 */
	public static void main(String[] args)throws Exception {
		String startingMainMethod = args[0];
		Method mainMethod = Class.forName(startingMainMethod).getMethod("main", String[].class);
		mainMethod.invoke(null,new Object[]{new String[]{"aaa","bbb"}});
		/**
		 *invoke()方法中的传参方式。
		 *1.当第一个参数为null时,则调用invoke方法的方法为static方法。
		 *2.第二个参数有两种写法:
                 *(1).new Object[]{new String[]{"aaa","bbb"}}因为main方法的参数是个字符串数组,
                 *   为了兼容jdk1.4所以必须采用把数组放到Object数组中,它运行时要进行解包。
		 *(2).(Object)new String[]{"aaa","bbb"}这种就是提醒编译器这是一个Object对象,不需要拆包。
		 */
	}

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


(二)数组反射


什么样的数组字节码是同一类型呢?

int[] a1 = new int[]{1,2,3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[]a4 = new String[]{"a","b","c"};
System.out.println(a1.getClass()==a2.getClass());//true
System.out.println(a1.getClass()==a4.getClass());//false
System.out.println(a1.getClass()==a3.getClass());//false
System.out.println(a1.getClass().getName());//[I表示的是int类型的数组
System.out.println(a1.getClass().getSuperClass().getName());//java.lang.Object
System.out.println(a4.getClass().getSuperClass().getName());//java.lang.Object


[结论]:具有相同的数组类型和具有相同的维度的数组字节码是同一类型。


(三)反射的作用--->实现框架功能

采用配置文件加反射的方式创建ArrayList和HashSet的实例对象与Eclipse对资源文件的管理方式。

配置文件:


config.propertise
 //className=java.util.ArrayList
className=java.util.HashSet


ReflectPoint类:



public class ReflectPoint {
	public int x;
	public int y;
	public ReflectPoint(int x, int y) {
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}
}


ReflectTest类:


import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Properties;

public class ReflectTest {

	public static void main(String[] args)throws Exception {
		InputStream ips = new FileInputStream("config.properties");
		Properties prop = new Properties();
		prop.load(ips);
		ips.close();
		String className = prop.getProperty("className");
		Collection collection = (Collection) Class.forName(className).newInstance();
		ReflectPoint rp = new ReflectPoint(3,3);
		ReflectPoint rp1 = new ReflectPoint(3,3);
		ReflectPoint rp2 = new ReflectPoint(4,2);
		ReflectPoint rp3 = new ReflectPoint(4,2);
		collection.add(rp1);
		collection.add(rp1);
		collection.add(rp2);
		collection.add(rp3);
		System.out.println(collection.size());
	}

}


[结论]当配置文件中配置的是ArrayList时,输出结果为:4。因为ArrayList的底层是数组,它的元素可以重复,所以添加多少就会有多少。

当配置文件中配置的是HashSet时,输出结果为:2。因为HashSet底层是哈希表结构,它依赖于HashCode()和equals()这两个方法保证元素的唯一性,不具有重复性。

(四)暴力反射

有些情况下,也许想要调用受保护的(protected)或私有(private)方法,可以使用Class的getDeclaredMethod()取得方法,并在调用Method的setAccessible()时指定为true。

当要取得受保护的(protected)或私有(private)属性,可以使用getDeclaredField()方法,并要调用Field的setAccessible()方法。

Method具体代码演示:

Method priMth = clz.getDeclaredMethod("priMth",...);
priMth.setAccessible(true);
priMth.invoke(target,args);




Field具体代码演示:


Class clz = Student.class;
Object o = clz.newIntance();
Field name = clz.getDeclaredField("name");
Field score = clz.getDeclaredField("score");
name.setAccessible(true);//如果是private的Field,要修改得调用此方法。
score.setAccessible(true);
name.set(o,"Justin");
score.set(o,90);
...


[结论]当用getDeclaredMethod()方法获得到受保护或者私有方法或属性时, 只是可以看的到而已,却不能拿来使用或修改,只有通过调用SetAccessble()方法之后才可以拿来使用或者修改。