java reflect(反射)

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]

猜你喜欢

转载自blog.csdn.net/qq_36154832/article/details/88035762