1.前言
个人对于反射的学习总结以及经验,在认真学习反射之前,一直不能理解为什么要用反射,也不知道反射主要运用点在哪里,但是听说反射很重要。
- 反射是什么?
反射的使用是一种动态编程,主要是可以在运行中创建对象,调用方法、可以获取类中的一切。 - 反射可以干什么?
(1)代理模式
代理模式被用在Spring的AOP中
代理模式在日志实现中例如log4j
(2)获取类中的全部信息(变量、方法这些,还要就是注解,自定义注解)。
(3)有一句话说的好,不存在完美的框架,虽然反射会破坏封装性,但是也可以让你修改原本封装好的私有变量,或者私有方法。
(4)跳过编译过程
注:代码段都是用了junit进行单员测试写的,需要获得源码可以去我的github获取
在reflex分支
我的github地址
2反射的学习
2.1 概念
在面向对象的世界里,万事万物皆对象。(java语言中,静态的成员、普通数据类型除外)
类是不是对象呢?类是(哪个类的对象呢?)谁的对象呢?
类是对象,类是java.lang.Class类的实例对象
2.2 这个对象到底如何表示
/**
* java是面向对象编程的语言,所以全部都是对象
* 类是不是对象?是谁的对象?
* 类是对象,类是java.lang.Class类的实例对象
*任何一个类都是Class的实例对象,这个实例对象有三种表示方式
*/
/**
* java是面向对象编程的语言,所以全部都是对象
* 类是不是对象?是谁的对象?
* 类是对象,类是java.lang.Class类的实例对象
*任何一个类都是Class的实例对象,这个实例对象有三种表示方式
*/
@Test
public void test2() throws ClassNotFoundException {
/**
* 说明每一个类中都有一个class静态变量
*/
Class s = Student.class;
/**
* 每一个对象中可以调用getClass()方法获取本对象Class
*/
Student a =new Student();
Class s1 = a.getClass();
/**
* 知道类的路径可以使用Class的静态方法来获取类
*/
Class s2 = Class.forName("com.imooc.reflex.Student");
/**
* 三种方法获取的类的类对象都是一样的
*/
System.out.println(s1 == s2);
System.out.println(s2 == s);
}
2.3 跳过编译过程
首先泛型的概念大家都知道、但是集合类中泛型在底层实现的时候存放的都是Object类型,基本所有的基本类型或者自己创建的对象都是Object的子类,所以我一个ArraysList 也可以存放String的变量,只要在编译阶段使用反射跳过泛型,不然直接调用add方法,编译会过不去,就会报错。
/**
* 泛型只是为了防止编译过程中传入不符合自己定义泛型的对象(输入错误)
* 还说明编译以后的泛型是去泛型化的,泛型只在编译阶段有效,反射会绕过编译
* 底层存储还是用Object类型存储的,下面就来证明这个。
* 这个例子就是往ArrayList<Interge>泛型中插入字符串
*/
@Test
public void test3() throws Exception {
ArrayList<Integer> arr1 = new ArrayList<Integer>();
arr1.add(1);
Class c = arr1.getClass();
Method m = c.getMethod("add", Object.class);
m.invoke(arr1,"字符串");
for(Object i : arr1){
System.out.println(i);
}
}
结果如下图:
可见ArraysList里面保存了字符串。
2.5获取基本的数据类型
/**
* 反射还能获取类中所有属性、方法、注解、构造方法、私有属性.....
*/
@Test
public void test4() throws Exception {
/**
* 获取属性,这样只能获取public 属性
*/
Class c = Person.class;
Field[] f = c.getFields();
for(Field i : f){
System.out.println(i.getName());
}
/**
* 获取本类所有私有的属性
*/
Field[] t = c.getDeclaredFields();
for(Field i:t){
System.out.println(i.getName());
}
}
2.5获取类中的所有方法
@Test
public void test4() throws Exception {
/**
*获取类中的所有方法并且使用
*/
Person t1 = (Person) c.newInstance();
Method[] m =c.getMethods();
for(Method i : m){
if(i.getName() .equals("setEmail") ){
i.invoke(t1,"1160827860@qq.com");
}else if(i.getName().equals("setName")){
i.invoke(t1,"lzy");
}else if(i.getName().equals("setAge")){
i.invoke(t1,21);
}
}
Method p = c.getMethod("print");
p.invoke(t1);
}
2.6获取构造方法,并且使用
@Test
public void test4() throws Exception {
/**
* 获取构造方法,并且使用
*/
Constructor construct = c.getConstructor(String.class,int.class,String.class);
t1 = (Person)construct.newInstance("sz",25,"a1160827860@163.com");
p.invoke(t1);
}
2.7其他需要注意的问题
2.7.1编译、运行中加载类
编译时刻加载类是静态加载类、运行时刻加载类是动态加载类
2.7.2其他的关键字
void关键字都存在类类型
2.7.3为什么要使用反射
就像底下这个代码一样:
比如我们完成了Peson类,但是noExist类不存在,如果采用new 创建一个对象的方式,就无法运行,如果使用反射,就可以运行。
1.在项目中如果要先对已经完成的代码,进行测试就可以采用动态加载的方式。
2.例如企业中更新软件,不是删除原来的代码,而是采用动态加载的方式
/**
* 静态加载在编译的时候加载,例如本例下面,如果没有Student
* 就会报错,但是我有Person类,我输入Person也不能过编译
* 动态加载就可以避免这种情况(也就是使用反射的方法)
* 静态加载引发的问题
*/
@Test
public void test(){
String chose = "Student";
if(chose.equals("Person")){
Person p = new Person();
p.toString();
}else if(chose.equals("Student")){
Student t = new Student();
t.toString();
}
// else{
// noExist no = new noExist();
// }
}
/**
* 使用动态加载,来解决上面这个问题
* 虽然没有noExist类但是可以允许使用其他分支
* 而第一个在编译阶段都过不去
*/
@Test
public void test1() throws Exception {
String chose ="Person";
if(chose.equals("Person")){
Person p = new Person();
System.out.println(p.toString());
}else if("noExist".equals(chose)){
Class c = Class.forName("com.imooc.reflex.noExist");
Object e = c.newInstance();
e.toString();
}
}