目录
1.定义:
Java的反射机制(reflection)机制就是在运行状态中,对于任何一个类,都能获取这个类的属性和方法。对于如何一个对象,都能够调用它任意的方法和属性(包括private修饰的字段),那么既然我们能够拿到这些方法和属性,那么我们当然就可以修改它们了。这种动态获取信息以及动态调用方法的功能就被称为Java语言的反射机制。
通俗地讲,反射就是把类里的属性和方法映射为一个个Java对象,我们通过操控这些对象来调用或修改类里的属性和方法。
想要解剖一个类,必然要拿到这个类的字节码文件对象。而解剖使用的就是Class类的方法。
Class类:
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件 ,被编译后的Java文件.class也被JVM解析为 一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类。
Class类没有公共的构造方法,Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
2.用途:
反射在实际项目中应用的非常的广泛,很多设计和开发都和反射有关,比如通过反射去调用字节码文件、调用系统隐藏 Api、动态代理的设计模式,Android 逆向、著名的 Spring 框架、各类 Hook 框架等等。
3.反射的使用:
我们来试着获取Student类的属性和方法:
package com.MyRefection;
public class Student {
private String name;
private int age;
public int id;
private Student(String name) {
this.name = name;
}
protected Student(int age) {
this.age = age;
}
public Student(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id;
}
public void study(int time) {
System.out.println(this.name + "学习了" + time + "个小时");
}
private void eat(String food) {
System.out.println(this.name + "中午吃了" + food);
}
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 int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
3.1获取class文件的三种方式:
第一种,使用 Class.forName("类的全路径名"); 静态方法。 前提:已明确类的全路径名。
此处,我们有一个Student类,我们要获取它的全路径->
点击这个Copy Reference就可以获取到我们的全路径啦
tips:这种方式获取的全路径只有复制到双引号""或注释里才会显示完整~~
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
//第一种,使用 Class.forName("类的全路径名"); 静态方法。 前提:已明确类的全路径名。
Class class1 = Class.forName("com.MyRefection.Student");
//我们来输出一下
System.out.println(class1);//运行结果:class com.MyRefection.Student
}
}
这种方法是最常用,最安全的。
第二种,使用 .class 方法。 说明:仅适合在编译前就已经明确要操作的 Class。
package com.MyRefection;
public class Main {
public static void main(String[] args){
//第二种,使用 .class 方法。 说明:仅适合在编译前就已经明确要操作的 Class。
Class class2 = Student.class;
//我们来输出一下
System.out.println(class2);//运行结果:class com.MyRefection.Student
}
}
第三种,使用类对象的 getClass() 方法。
package com.MyRefection;
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
//第三种,使用类对象的 getClass() 方法。
Student student = new Student("张三", 16, 116);
Class class3 = student.getClass();
//我们来输出一下
System.out.println(class3);//运行结果:class com.MyRefection.Student
}
}
使用这种方法要先创建该类的实例对象。
那么用这三种方法创建的Class对象是否相等呢?
System.out.println(class1 == class2 && class2 == class3);//运行结果:true
答案是true~~
3.2反射获取构造函数:
方法 | 功能 |
getConstructor(Class... parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class... parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
newInstance() | 创建实例 |
setAccessible(boolean flag) | 表示是否取消权限的校验 |
注意:带有Declared字眼的就代表可以获取所有方法或属性(包括private修饰的)
1.getConstructors()
package com.MyRefection;
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.创建Constructor[]数组来获取构造方法
Constructor[] constructors1 = clazz.getConstructors();
//遍历一下看看效果:
for (Constructor constructor : constructors1) {
System.out.println(constructor);
}
//执行结果:
//public com.MyRefection.Student(java.lang.String,int,int)
//只能访问到public修饰的构造函数
}
}
2.getDeclaredConstructors()
package com.MyRefection;
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.使用getDeclaredConstructors方法来获取全部构造方法
Constructor[] constructors2 = clazz.getDeclaredConstructors();
//遍历一下看看效果:
for (Constructor constructor : constructors2) {
System.out.println(constructor);
}
//执行结果:
//public com.MyRefection.Student(java.lang.String,int,int)
//protected com.MyRefection.Student(int)
//private com.MyRefection.Student(java.lang.String)
//可以访问其他修饰符(private/protected)修饰的构造函数
}
}
3.getConstructor(Class... parameterTypes)
package com.MyRefection;
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.获取单个构造函数
Constructor constructor1 = clazz.getConstructor();
//输出一下看看效果
System.out.println(constructor1);
//执行之后报错了~~
}
}
原因是我们Student的构造函数中并没有无参构造函数,我们想要调用哪个函数,就要在getConstructor()中加该函数的参数类型的字节码文件,我们以这个含三个参数的构造方法为例:
想要获取这个构造函数,应该这样写:
package com.MyRefection;
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.获取单个构造函数
Constructor constructor1 = clazz.getConstructor(String.class, int.class, int.class);
//输出一下看看效果
System.out.println(constructor1);
//执行结果:public com.MyRefection.Student(java.lang.String,int,int)
}
}
注意,
这里传递的是参数类型的字节码文件!!!
4.getDeclaredConstructor(Class... parameterTypes)
注意getConstructor(Class... parameterTypes)并不能获取私有的构造函数,这时候就要使用getDeclaredConstructor(Class... parameterTypes):
被private修饰
package com.MyRefection;
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.获取单个构造函数(包含被private/protected修饰的)
Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
//输出一下看看效果
System.out.println(constructor2);
//执行结果:
//private com.MyRefection.Student(java.lang.String)
}
}
OK,现在我们获取到这个构造函数了,那我们可以使用它来做什么事情呢?
1.我们可以使用constructor2这个对象来查看它所指向的构造函数是什么类型的:
//我们可以使用constructor2这个对象来查看它所指向的构造函数是什么类型的
System.out.println(constructor2.getModifiers());
//执行结果:2
为什么执行结果是2呢?
原因是通过这个函数我们获取到的是常量字段值,是int类型,而不是String类型,下面是反射的常量字段值:
可以看到private的常量字段值是2~~
2.获取构造方法的参数:
package com.MyRefection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.获取单个构造函数
Constructor constructor1 = clazz.getConstructor(String.class, int.class, int.class);
//输出一下看看效果
System.out.println(constructor1);
//执行结果:
//public com.MyRefection.Student(java.lang.String,int,int)
//3.获取三个参数的构造函数的参数
Parameter[] parameters = constructor1.getParameters();
//打印一下
for (Parameter parameter : parameters) {
System.out.println(parameter);
}
//结果:
//java.lang.String arg0
//int arg1
//int arg2
}
}
3.使用反射创建对象:
package com.MyRefection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.获取单个构造函数(包含被private/protected修饰的)
Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
//输出一下看看效果
System.out.println(constructor2);
//执行结果:
//private com.MyRefection.Student(java.lang.String)
//3.使用反射创建对象,这里我们使用private修饰的构造函数来创建对象
Student student = (Student) constructor2.newInstance("张三");
//执行结果:报错了~~
}
}
原因是这个构造函数被private修饰,不能直接创建,我们需要使用setAccessible(boolean flag)来取消权限的校验:
//3.使用反射创建对象,这里我们使用private修饰的构造函数来创建对象
constructor2.setAccessible(true);//设置为true表示取消对权限的校验
Student student = (Student) constructor2.newInstance("张三");
//打印一下这个student对象
System.out.println(student);
//执行结果:com.MyRefection.Student@1b6d3586
注意如果这个构造函数是public修饰的则可以不用加setAccessible(boolean flag)语句~~
3.3反射获取成员变量:
方法 | 功能 |
setAccessible(boolean flag) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值 |
前四个方法与前面反射获取构造函数大相径庭,这里就不过多讲解了(直接上代码):
package com.MyRefection;
import java.lang.reflect.Field;
public class Main2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.创建Field数组来获取公有的成员变量
Field[] fields1 = clazz.getFields();
//遍历一下
for (Field field : fields1) {
System.out.println(field);
}
//输出结果:
//public int com.MyRefection.Student.id
//3.创建Field[]数组来获取所有成员变量
Field[] fields2 = clazz.getDeclaredFields();
//遍历一下
for (Field field : fields2) {
System.out.println(field);
}
//输出结果:
//private java.lang.String com.MyRefection.Student.name
//private int com.MyRefection.Student.age
//public int com.MyRefection.Student.id
//4.获取单个公有的成员变量
Field field3 = clazz.getField("id");//传递的参数是想要获取的成员变量的名称
//输出一下
System.out.println(field3);
//输出结果
//public int com.MyRefection.Student.id
//5.获取单个私有的成员变量
Field field4 = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称
//输出一下
System.out.println(field4);
//输出结果
//private java.lang.String com.MyRefection.Student.name
}
}
主要为大家讲解一下get和set方法:
get():
package com.MyRefection;
import java.lang.reflect.Field;
public class Main2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.获取单个私有的成员变量
Field name = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称
//3.使用这个name来获取成员变量的值
//要想获得成员变量的值首先要创建对象
Student student = new Student("张三", 16, 116);
Object value = name.get(student);//这里传递的参数是创建的对象
System.out.println(value);
//执行结果:出错啦~~
}
}
出错的原因是这个成员变量被private修饰,不能直接获取,要使用setAccessible(boolean flag)来取消权限的校验:
//2.获取单个私有的成员变量
Field name = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称
//3.使用这个name来获取成员变量的值
//要想获得成员变量的值首先要创建对象
Student student = new Student("张三", 16, 116);
//4.取消访问权限的校验
name.setAccessible(true);
//5.获取成员变量的值
Object value = name.get(student);//这里传递的参数是创建的对象
System.out.println(value);
//执行结果:张三
set():
package com.MyRefection;
import java.lang.reflect.Field;
public class Main2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.获取单个私有的成员变量
Field name = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称
//3.使用这个name来获取成员变量的值
//要想获得成员变量的值首先要创建对象
Student student = new Student("张三", 16, 116);
//4.同样取消访问权限的校验
name.setAccessible(true);
//5.使用这个name来修改成员变量的值:张三 -> 李四
name.set(student, "李四");
System.out.println(student.getName());
//执行结果:李四
}
}
3.4反射获取成员方法:
方法 | 功能 |
getMethod(String name, Class... parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class... parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
Object invoke(Object obj, 0bject... args) | 运行方法 |
我们来看一下这个getMethods()可以获取到什么方法
package com.MyRefection;
import java.lang.reflect.Method;
public class Main3 {
public static void main(String[] args) throws ClassNotFoundException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.创建Method[]数组来获取所有公有成员方法
Method[] methods1 = clazz.getMethods();
//遍历一下
for (Method method : methods1) {
System.out.println(method);
}
}
}
执行结果:
从执行结果可以发现,除了有本身的成员方法外,还有继承自Object类的常用方法~~
同样前四个方法和之前的大相径庭,就不做详细讲解了(直接上代码)~~
package com.MyRefection;
import java.lang.reflect.Method;
public class Main3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//2.创建Method[]数组来获取所有公有成员方法
Method[] methods1 = clazz.getMethods();
//3.创建Method[]数组来获取所有成员方法(包括private/protected修饰的)
Method[] methods2 = clazz.getDeclaredMethods();
//4.获取单个公有成员方法
Method method3 = clazz.getMethod("study", int.class);
//第一个参数是方法名,第二个参数是要传入的参数的类型
//5.获取单个公有成员方法
Method method4 = clazz.getDeclaredMethod("eat", String.class);
//第一个参数是方法名,第二个参数是要传入的参数的类型
}
}
invoke方法
package com.MyRefection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//1.获取.class字节码文件
Class clazz = Class.forName("com.MyRefection.Student");
//以私有方法为例:
//2.获取单个公有成员方法
Method method4 = clazz.getDeclaredMethod("eat", String.class);
//第一个参数是方法名,第二个参数是要传入的参数的类型
//3.创建Student类的对象
Student student = new Student("张三", 16, 110);
//4.取消访问权限的校验
method4.setAccessible(true);
//5.调用invoke方法
method4.invoke(student, "汉堡包");
//第一个参数为创建的对象,之后的参数是要调用方法的参数
//执行结果:张三中午吃了汉堡包
}
}
注意:同样的,如果要调用的方法是private/protected修饰的,也要取消访问权限的校验。
4.反射的利与弊:
反射的好处:
1.对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。
2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力。
3. 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。
反射的坏处:
1. 使用反射会有效率问题,会导致程序效率降低。具体参考这里:(6条消息) 反射真的很耗时吗,反射 10 万次,耗时多久。_Q.E.D.的博客-CSDN博客
2. 反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂 。