1、什么是反射技术?
反射技术是指动态获取指定类以及类中的内容(成员),并运行其内容。
反射用在框架中(编写框架),脱离了框架,反射单独使用没有太大的作用。反射用在自己封装一些工具(框架!),如果不深入学习框架的底层,那么反射这块知识很少用到。
应用程序已经运行,无法在其中进行new对象的建立,就无法使用对象。这时可以根据配置文件的类全名去找对应的字节码文件(.class文件),并加载进内存,并创建该类对象实例。这就需要使用反射技术完成。
2、获取class对象的三种方式
首先创建一个实体类:
package cn.single;
public class Person {
private int age;
private String name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public Person() {
super();
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Persion [age=" + age + ", name=" + name + "]";
}
public String showInfo() {
return "showInfo!";
}
public String showInfo(String str) {
return "showInfo!"+str;
}
}
方式一:通过对象具备的getClass方法。
//创建Peron对象
Person p = new Person();
//通过object继承来的方法(getClass)获取Person对应的字节码文件对象
Class clazz = p.getClass();
方式二:通过类获取
Class clazz = Person.class;
方式三:通过类全面获取
//必须类全名
Class clazz = Class.forName("cn.tit.bean.Person");
3、通过反射创建对象
通过forName()根据指定的类名称去查找对应的字节码文件,并加载进内存。并将该字节码文件封装成了Class对象。直接通过newIntstance()方法,完成该对象的创建。
newInstance方法调用就是该类中的空参数构造函数完成对象的初始化。
//根据名称获取其对应的字节码文件对象
Class clazz = Class.forName("cn.single.Person");
//通过Class的方法完成该指定类的对象创建。
Object object = clazz.newInstance();
通过指定的构造器对象进行对象的初始化。
首先在person类中添加一个构造方法
private Person(int age) {
super();
this.age = age;
}
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("cn.single.Person");
//获取一个指定的公共的构造函数,仅public
Constructor constructor =clazz.getConstructor(int.class,String.class);
Object obj = constructor.newInstance(29,"jiao");
System.out.println(obj);
//获取一个指定的任意构造函数
Constructor constructor2 = clazz.getDeclaredConstructor(int.class);
//如果构造函数是私有的,设置一下访问权限
constructor2.setAccessible(true);
Object obj2 = constructor2.newInstance(1);
System.out.println(obj2);
}
返回结果
Persion [age=29, name=jiao]
Persion [age=1, name=null]
4、获取字节码文件中的字段
public static void test1()throws Exception{
Class clazz = Class.forName("cn.single.Person");
//获取所有字段
Field[] fields = clazz.getDeclaredFields();
//获取该类中的指定字段。比如age
Field field = clazz.getDeclaredField("age");//clazz.getField("age");
Object obj = clazz.newInstance();
//对私有访问,必须取消对其的访问控制检查,使用setAccessible的方法
field.setAccessible(true);
field.set(obj, 26);
//获取该字段的值。
Object o = field.get(obj);
System.out.println(o);
System.out.println(obj);
}
执行结果
26
Persion [age=26, name=null]
5、获取字节码文件中的方法
public static void test2()throws Exception{
Class clazz = Class.forName("cn.single.Person");
//获取所有方法
Method[] methods = clazz.getMethods();
//获取无参方法,同clazz.getMethod("showInfo")
Method method = clazz.getMethod("showInfo", null);
Object obj = clazz.newInstance();
//调用无参方法,同method.invoke(obj)
System.out.println(method.invoke(obj, null));
//获取有参数构造,调用执行
Method method1 = clazz.getMethod("showInfo", String.class);
System.out.println(method1.invoke(obj, "jdk"));
}
执行结果
showInfo!
showInfo!jdk
6、应用举例
1、获取一个对象的所有字段,把其中String类型的字段值中的’h’全改成’x’
思路:获取对象的Class对象;获取对象所属类的所有字段;循环遍历,拿到每个字段;判断是否是String类型; 否:不管; 是E:取出字段的值;把其中的h替换成x;把修改好的字符串设置给这个字段
public static void main(String[] args) throws Exception {
Person s = new Person(40, "hengtonghong");
// A:获取对象的Class对象
Class clazz = s.getClass();
// B:获取对象所属类的所有字段
Field[] fields = clazz.getDeclaredFields();
// C:循环遍历,拿到每个字段
for( Field f : fields){
// 暴力访问
f.setAccessible(true);
// D:判断是否是String类型
if(f.getType() == String.class){
// 是:E:取出字段的值
String str = (String) f.get(s);
// F:把其中的h替换成f
str = str.replace('h', 'x');
// G:把修改好的字符串设置给这个字段
f.set(s, str);
}
}
System.out.println(s);
}
返回结果:
Persion [age=40, name=xengtongxong]
2、通过配置文件实现类的切换
将beans.ini文件放到src目录下
className=java.util.ArrayList
public static void main(String[] args) throws Exception {
// 加载配置文件
Properties prop = new Properties();
InputStream in = SingleObject.class.getResourceAsStream("/beans.ini");
prop.load(in);
// 获取类的名称
String className = prop.getProperty("className");
// 获取类的字节码文件对象
Class<?> clazz = Class.forName(className);
// 反射创建对象
// 创建集合
Collection<String> c = (Collection<String>) clazz.newInstance();
// 添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("hello");
c.add("java");
// 打印集合
System.out.println(c);
}
运行结果:
[hello, world, java, hello, java]
3、ArrayList<Integer>中添加一个字符串数据
泛型会对数据的类型做限定,因此这里只能添加Integer,不能添加String;但是,我们知道泛型是一个编译期技术,一旦编译成class文件,就会被擦除。因此,我们可以知道ArrayList的所有泛型方法,在编译成class文件后,其实都是没有泛型的。也就是说:它的add方法,在字节码中,其实接收的是Object类型。那么,我们如果用反射来获取ArrayList的方法,就能绕过泛型的检查了。
public static void main(String[] args) throws Exception {
// 创建集合
ArrayList<Integer> al = new ArrayList<>();
// 添加元素
al.add(100);
// 获取ArrayList的字节码
Class<? extends ArrayList> clazz = al.getClass();
// 获取它的add方法
Method method = clazz.getMethod("add", Object.class);
// 反射调用
method.invoke(al, "hello");
method.invoke(al, "world");
method.invoke(al, "java");
System.out.println(al);// [100, hello, world, java]
}
运行结果:
[100, hello, world, java]