这一篇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()方法之后才可以拿来使用或者修改。