今天跟着blog传送门复习下反射机制。
反射的概念
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法。对于任意的对象,都能够调用它的任意一个方法和属性,这种动态的获取信息以及动态调用对象方法的功能就被称为反射机制。
想要使用反射机制,必须先要获取该类的字节码文件对象(.class),通过字节码文件对象就能获得该类方法中我们想要获取的信息,每一个类对应一个字节码文件也就是对应一个Class类型的对象,也就是字节码文件对象。
获取字节码文件的三种方式:
- Class clazz1 = Class.forName(“全限定类名”) ;
通过forName直接获取到一个类的字节码文件对象,此时这个类还是源文件阶段,并没有变为字节码文件。 - Class clazz2 = Person.class ;
当类被加载为.class文件时,此时Person类变成了.class,再获取了该字节码文件对象,就是获取了自己,该类处于字节码阶段。 - Class clazz3 = p.getClass() ;
通过对象p的getClass方法得到该类的字节码文件对象,该类此时处于创建对象阶段。
反射机制能够获得什么信息?
- 通过字节码对象创建对象:
Class clazz1 = Class.forName("Rikka.Person");
Person person = (Person)clazz1.newInstance();
- 获取指定构造器方法。因为通过newInstance构造的是无参的构造方法,如果Person的构造方法都是有参数的话就用Constructor:
Class clazz2 = Class.forName("Rikka.Person");
//getConstructor中为参数列表,有多少写多少,也可以不写
Constructor constructor = clazz2.getConstructor(String.calss,String.class,int.class);
Person person = (Person)constructor.newInstance("小谢","男",21);
传送门这里是一个通过反射获取对象的方法。
如果一个类的构造方法为private,这代表我们无法通过new来构造一个对象,那么我们可以通过使用单例模式来获取一个单例对象(单例模式中构造方法就是private),第二个方法就是通过上述所用到的反射来获取啦!
获取全部构造方法:
Class clazz3 = Class.forName("Rikka.Person");
Constructor[] constructors = clazz3.getConstructors();
//遍历所有构造方法.
for(int i =0;i < constructors.lenth;i++){
//得到每个构造函数的参数类型字节码对象
Class[] parameterTypes = constructors.getParameterTypes();
System.out.println("第"+i+"个构造函数");
for(int j =0;j < parameterTypes.lenth;j++){
System.out.println(parameterTypes[j].getName()+",");
}
}
- 获取成员变量,使用Field
首先要知道Person中要访问的是共有变量还是私有变量,不同访问级别用的方法不一样。
Class clazz4 = Class.forName("Rikka.Person");
Person person = (Person)clazz4.newInstance();
//如果变量不是私有的,通过clazz3.getField(name),通过name来获取指定的成员变量
//如果成员变量是私有的,通过getDeclaredField(name)获取
Field field1 = clazz3.getField("name");
Field field2 = clazz3.getDeclaredField("Id");
//因为属性是私有的,获得属性之后,还有让其打开可见权限
field2.setAccessible(true);
//对其变量进行复制操作.
field1.setString(name,"小陈");
field2.setInt(Id,150307);
//获取成员变量的值,通过field.get(obj) obj为所表示字段的对象
System.out.println(field2.get(person));
获取全员变量
Class clazz5 = Class.forName("Rikka.Person");
Person person = (Person)clazz5.newInstance();
person.setSex("男");
person.setName("小郑");
//获取所有私有属性
Field[] fields = clazz5.getDeclaredFields();
for(int i =0;i < fields.lenth;i++){
fields[i].setAccessible(true);
System.out.prinln(fields[i].get(person));
}
- 获得方法使用 Method
Class clazz6 = Class.forName("Rikka.Person");
Person person = (Person)clazz6.newInstance();
/**
如果为不带参数的方法,比如Person中的run()
**/
Method method1 = clazz6.getMethod("run");
/**
通过invole调用这个方法, invoke(obj,args)
obj为要使用方法的对象,args为参数列表,没有则不填
**/
method1.invoke(person);
//带参数的方法,比如Person中eat(String food)
Method method2 = clazz6.getMethod("eat",String.class);
method2.invoke(person,"三文鱼");
//获得私有方法,比如Person中pay(int money)
Method method3 = clazz6.getDeclaredMethod("pay",int.class);
method3.setAccessible(true);
method3.invoke(person,443);
获取所有方法,同理啦。
Method[] methods = clazz6.getDeclaredMethods();
Person person = (Person)clazz6.newInstance;
for(Method method : methods){
method.setAccessible(true);
System.out.prinln(method.getName());
//获得方法参数,又回到了之前的代码
Class<?>[] parameterTypes = method.getParmeterTypes();
for(int j =0;j<parameterTypes.lenth;j++){
System.out.println(parameterTypes[j].getName()+",");
}
}
- 获取该类所有的接口
Class[] getInterfaces() :返回一个此对象所表示接口的类或者实现的接口的数组 - 获取指定资源的输入流
InputStream getResourceAsStream(Sting name)
return :一个InputStream对象 - 对于动态代理的影响
动态代理其实就是通过反射来生成一个代理。
在java.lang.reflect中提供了Proxy类和一个InvocationHandler接口,通过这个类和接口就可以生成动态代理对象。JDK中提供的代理只能针对接口代理。Proxy类中的方法创建动态代理对象分三步:
(1)new出代理对象:通过实现InvocationHandler接口,然后new出代理对象
(2)通过Proxy中的静态方法newProxyInstance,来将代理对象假装成那个被代理的对象(就跟替接了你的身份证帮你买车票一样)
(3)执行成功,代理成功。
下面为实现:
public class MyInvocationHandler implements InvocationHandler{
private Object target;
public MyInvocationHandler(Object target){
this.target = targetl
}
@Override
public Object invoke(Object proxy,Method method, Object[] args) throws Throwable{
System.out.println("权限校验");
method.invoke(target,args); //执行被代理target对象的方法
retrun null;
}
}
//想要代理方法的接口,比如说我是一个学生,我可以做login和submit两个动作
public interface Student{
public void login();
public void submit();
}
//本类,就是我这个学生,我login和submit时是怎么样做的。代理类代理的就是这个类。
public class StudentImp implments Student{
@Override
public void login(){
System.out.prinln("login!");
}
@Override
public void sumbit(){
System.out.prinln("submit!");
}
}
最后在我们要执行的地方
//new出一个被代理的类
Student simp = new StudentImp();
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
MyInvocationHandler m = new MyInvocationHander(simp);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 m.getClass().getClassLoader() ,我们这里使用m这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数simp.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,
* 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数m, 我们这里将这个代理对象关联到了上方的MyInvocationHandler 这个对象上
*/
Student studentPoxy = (Student)Proxy.newProxyInstance(m.getClass().getClassLoader(),simp.getClasss().getInterfaces(),m);
//这里就可以通过代理来执行其方法了。
studentProxy.login();
studentProxy.submit();
- 还有很多方法,比如获得类的加载器等等,具体就要参考官方文档的api了。
应用实例
在一个泛型为Integer的ArrayList中存放一个String的对象
原理:集合的泛型只在编译的时候有效, 而到了运行期,泛型则会失效.
List<Integer> list = new ArrayList<Integer>();
list.add(4);
list.add(5);
Class clazz=list.getClass();
Method method = clazz.getMethod("add",Integer.class);
method.invoke(list,"aaaa");
System.out.println(list);
输出结果将是:
4 5 aaaa
总结:
上面是浅析反射机制,反射主要作用就是我们可以在任意时间获取任意类的方法属性,管它是不是私有的。通过反射也能解决和实现一些问题。