反射 是 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);//NoSuchFieldException
System.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) 方法
除了以上最常用的获取 构造方法 、成员变量 、成员方法 ,在框架中还经常通过反射获取 类 、 属性 、方法 上的注解信息
有需要可以看我的另外一篇博客,地址:
而且我的 Gitee 上有 Tomcat 、SpringMVC 等框架的核心源码仿写,可以感受下反射在框架中的使用,Gitee 地址如下: