反射 是 Java框架 的核心 ,无论是Tomcat、SpringMVC、Spring IOC、Spring AOP、动态代理 ,都使用到了 反射
反射的作用简单讲就是 无需 new 对象,就可以动态获取到一个类的全部信息,包括 属性、方法,构造器,以及他们的修饰符、参数、注解 等等... 从而构造出 对象实例 并对 对象实例 进行操作
因此,学好反射是必须的,接下来就学习如何使用反射
这是我们下面要进行操作的类
public class Student {private String name;private int age;public String gender;public String address;public Student() {}public Student(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}public Student(String name, int age, String gender, String address) {this.name = name;this.age = age;this.gender = gender;this.address = address;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}private void study(){System.out.println("学生在学习");}private void sleep(){System.out.println("学生在睡觉");}public String eat(String something){System.out.println("学生在吃" + something);return "学生已经吃完了,非常happy";}public String toString() {return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";}
}
获取字节码文件对象(即 类对象)的三种方式
- 通过 Class类 里面的 静态方法forName("全类名")(最常用)
- 通过 类名.class 属性获取
- 通过对象获取字节码文件对象 -- 对象名.getClass()
什么是字节码文件对象?
java文件:就是我们自己编写的java代码。
字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)
字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。这个对象里面就包含了类的所有信息。并且该对象在内存中是唯一的(存在于 JVM 的方法区中)
往后 获取类信息 的操作都是建立在 字节码文件对象 的基础上的
这三种获取 字节码文件对象 的方法是有区别的
- Class.forName("全类名") -- 在 java文件阶段 就可以获取 字节码文件对象,因此这是最常用的
- 类名.class -- 要在 类加载完成阶段 才能获取
- 对象名.getClass) -- 要在 对象已经被创建出来的阶段 才能获取
代码示例
//第一种方法
Class clazz1 = Class.forName("com.zhuyuanjie.reflectdemo.Student");//第二种方法
Class clazz2 = Student.class;//第三种方法
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz2 == clazz3);//true
获取 构造方法 并使用
不许通过 new ,我们也能通过反射获取到实例对象
大致步骤 -- 获取构造器对象,调用构造器的 newInstance 方法就可以创建出实例对象
——— 1.首先获取构造器对象 ————
方法如下:
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 获得所有的构造(只能public修饰) |
Constructor<?>[] getDeclaredConstructors() | 获得所有的构造(包含private修饰) |
Constructor getConstructor(Class<?>... parameterTypes) | 获取指定构造(只能public修饰) |
Constructor getDeclaredConstructor(Class<?>... parameterTypes) | 获取指定构造(包含private修饰) |
观察上面表格发现,方法中如果没加 Declareed 就只能获取 public 的构造方法,而加了 Declareed 就可以获取全部,
而且方法末尾如果有 s 是获取所有,没加 s 是获取指定的构造器,需传入 指定构造器的参数列表
代码实例
//获得class字节码文件对象Class clazz = Class.forName("com.zhuyuanjie.reflectdemo.Student");//获取构造方法对象//获取所有构造方法(public)Constructor[] constructors1 = clazz.getConstructors();for (Constructor constructor : constructors1) {System.out.println(constructor);}//获取指定的空参构造Constructor con1 = clazz.getConstructor();System.out.println(con1);//获取带参数的构造方法Constructor con2 = clazz.getDeclaredConstructor(String.class);System.out.println(con2);
——— 2 调用 newInstance 方法,创建 实例对象———
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.zhuyuanjie.reflectdemo.Student");
//2.获取空参的构造方法
Constructor con = clazz.getConstructor();
//3.利用空参构造方法创建对象
Student stu = (Student) con.newInstance();
System.out.println(stu);
上面代码中,如果我们获取到的构造方法是 private 属性,那么我们还不能直接调用,需要调用 setAccessible(true)方法 传入参数 true,来临时取消访问权限,如下:
Class clazz = Class.forName("com.zhuyuanjie.reflectdemo1.Student");Constructor con = clazz.getConstructor();//调用 setAccessible(true) 方法
con.setAccessible(true);Student stu = (Student) con.newInstance();
System.out.println(stu);
该方法不止用在构造方法上,包括接下来的 属性、方法 等,只要对 private修饰 的进行操作,就需要调用
获取 成员变量(属性) 并使用
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
可以发现以上方法名的结构,与 获取构造方法使用到的方法 一样,这里不在赘述
代码实例
//1.获取class对象Class clazz = Class.forName("com.zhuyuanjie.reflectdemo.Student");//获取成员变量的对象(public + private)Field[] fields2 = clazz.getDeclaredFields();for (Field field : fields2) {System.out.println(field);}System.out.println("===============================");//获得单个成员变量对象//如果获取的属性是不存在的,那么会报异常//Field field3 = clazz.getField("aaa");//System.out.println(field3);//NoSuchFieldExceptionSystem.out.println("===============================");//获取单个成员变量(私有)Field field5 = clazz.getDeclaredField("name");System.out.println(field5);
获取到 Field 对象后,我们就能得到 成员变量 的一切信息
//1.获取class对象Class clazz = Class.forName("com.zhuyuanjie.reflectdemo.Student");Field name = clazz.getDeclaredField("name");//临时取消访问权限name.setAccessible(true);//获取权限修饰符int modifiers = name.getModifiers(); //2//获取属性名String n = name.getName(); // name//获取数据类型Class<?> type = name.getType(); //class java.lang.String//等等... 还有很多,不在展示
还可以获取和修改 成员变量 的值
方法 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值 |
Class clazz = Class.forName("com.zhuyuanjie.reflectdemo.Student");Field field = clazz.getDeclaredField("name");field.setAccessible(true);//设置(修改)name的值//参数一:表示要修改哪个对象的name?//参数二:表示要修改为多少?field.set(s,"张三");//获取name的值//表示我要获取这个对象的name的值String result = (String)field.get(s); // 张三
获取 成员方法 并运行
获取 成员方法 的方法和获取 成员的变量 的方法几乎一样,就是把 Field 改为 Method
而 获取到 成员方法 后,通过调用 Object invoke(Object obj, Object... args) 进行运行
其中
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
//1.获取字节码文件对象Class clazz = Class.forName("com.zhuyuanjie.reflectdemo.Student");//2.获取一个对象//需要用这个对象去调用方法Student s = new Student();//3.获取一个指定的方法//参数一:方法名//参数二:参数列表,如果没有可以不写Method eatMethod = clazz.getMethod("eat",String.class);//运行//参数一:表示方法的调用对象//参数二:方法在运行时需要的实际参数//注意点:如果方法有返回值,那么需要接收invoke的结果//如果方法没有返回值,则不需要接收String result = (String) eatMethod.invoke(s, "重庆小面");System.out.println(result);
同样的,如果方法是 private ,也需要调用 setAccessible(true) 方法
除了以上最常用的获取 构造方法 、成员变量 、成员方法 ,在框架中还经常通过反射获取 类 、 属性 、方法 上的注解信息
有需要可以看我的另外一篇博客,地址:
http://t.csdn.cn/qyDp0http://t.csdn.cn/qyDp0
而且我的 Gitee 上有 Tomcat 、SpringMVC 等框架的核心源码仿写,可以感受下反射在框架中的使用,Gitee 地址如下:
朱元杰的Gitee -- 框架源码仿写https://gitee.com/Speed_Demon/projects