反射(超级详细)
一:什么是反射?
Java官方对反射的解释:反射允许对封装类字段(成员变量),方法(成员方法)和构造函数(构造方法)的信息进行编程访问。
我们知道在一个类的里面,经常使用的有下面三部分内容,分别是成员变量、构造方法、成员方法。反射可以把他理解成一个人,这个人可以把成员变量、成员方法、构造方法都获取出来,并对他们进行操作。
获取出来有什么用?
比如idea里面的自动提示功能,其实就是用反射来实现的。比如创建了一个Student的对象,用对象去调用方法或者是成员变量,idea就会利用反射把这个类里面能调用的所有方法,所有成员变量都获取出来并进行展示。
还有当我们在创建对象或者是调用方法的时候,方法的形成忘了,ctrl + p可以提示,idea也是利用反射获取到这个方法上面所有的形参并展示出来。
为什么一定要用反射?IO流不行吗?从上往下一行一行的读,一行一行的拿不是也可以吗?
IO流我们是从上往下一行一行去读,当我们读到构造方法和普通成员方法的时候,怎么区分?如果用返回值区分,成员变量和局部变量你怎么区分?还得通过上下文去区分非常的麻烦。所以在这个时候就可以使用反射了。
利用反射我们可以把成员变量获取出来,获取出来就可以得到这个成员变量的所有信息了,所有的!比如获取修饰符、名字、类型、进行赋值或者获取已经记录的值。
利用反射我们还可以获取一个类里面的构造方法,并且所有的信息也能获取到。比如修饰符、名字、形参、甚至还能利用获取出来的构造方法去创建对象。
利用反射还可以获取到一个类里面的成员方法,并且所有的信息也能获取到。比如修饰符、名字、形参、返回值、抛出的异常、注解、运行方法。
总之,利用反射可以获取类里面的所有信息!!
注意点:在获取的时候,我们不是从Java文件中去获取的,而是从class字节码文件当中去获取的。所以要先学习如何获取class字节码文件的对象,再去学习如何从字节码文件里面去获取到字段、构造方法、成员方法。然后再去获取里面的每一个信息。
二:获取class对象的三种方式
(1)Class.forName(“全类名”)
(2)类名.class
(3)对象.getClass()
这三种方式,什么时候用哪一种呢?
这三种方式其实就对应Java里面三种不同的阶段,如果我想创建一个类的对象,是分以下三个阶段的:
第一个阶段,要先编写Java文件,然后把他编译成.class字节码文件,这个阶段还没有把代码加载到内存的,只是在硬盘里面进行的操作,所以这个阶段我们也叫做源代码阶段,这个阶段我们会用第(1)种方式去获取字节码文件对象
接下来要运行代码了,是不是首先要把这个类的字节码文件加载到内存?这个阶段我们叫做加载阶段,在这个阶段我们会用第(二)种方式
接下来去内存当中创建这个类的对象,比如A a = new A(),此时就叫做运行阶段,这个阶段用第三种方式。
public class test1 {
public static void main(String[] args) throws ClassNotFoundException {
// 第一种方式
// 全类名:包名 + 类名
// 最为常用
Class<?> clazz1 = Class.forName("com.test.test2.Student");
System.out.println(clazz1); // class com.test.test2.Student
// 第二种方式
// 一般更多是当做参数传递
// 比如多线程的同步代码块锁的对象
Class<Student> clazz2 = Student.class;
System.out.println(clazz2); // class com.test.test2.Student
// 第三种方式
// 当已经有了这个类的对象的时候才可以使用
Student student = new Student();
Class<? extends Student> clazz3 = student.getClass();
System.out.println(clazz3); // class com.test.test2.Student
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz2 == clazz3); // true
}
}
class Student{
private String name;
private int age;
// 标准JavaBean
}
三:什么是反射?
刚刚已经学习了如何获取字节码文件的对象,接下来就要从字节码文件里面去获取构造方法、成员变量和成员方法。在Java里面有个思想,叫做“万物皆对象”,简单来说就是什么玩意儿都可以把他看作是一个对象。比如说字节码文件,可以看作是Class这个类的对象。同理构造方法也可以把他看做是一个对象,谁的对象?Constructor类,这个类就是用来描述构造方法的,同理这个类的对象就表示构造方法的对象。Field是用来描述成员变量的,所以这个类的对象就表示成员变量的对象。Method用来描述成员方法,这个类的对象就是成员方法的对象。
四:利用反射获取构造方法
Class类中用于获取构造方法的方法:
1.Constructor<?>[] getConstructors(); 返回所有“公共”构造方法对象的数组
2.Constructor<?>[] getDeclaredConstructors(); 返回所有构造方法对象的数组
3.Constructor getConstructor(Class<?>…parameterTypes); 返回单个“公共”构造方法对象
4.Constructor getDeclaredConstructor(Class<?>…parameterTypes); 返回单个构造方法对象
Constructor类中用于创建对象的方法:
1.T newInstance(Object… initargs): 根据指定构造方法创建对象
2.setAccessible(boolean flag): 设置为true,表示取消访问检查
public class Student {
private String name;
private int age;
private Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
public Student(String name) {
this.name = name;
}
protected Student(int age) {
this.age = age;
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1.获取class字节码文件的对象
Class<?> clazz = Class.forName("com.test.reflact.Student");
// 2.获取构造方法
Constructor<?>[] cons1 = clazz.getConstructors();
for (Constructor<?> con : cons1) {
System.out.println(con);
// public com.test.reflact.Student(java.lang.String)
// public com.test.reflact.Student()
}
System.out.println("=======================");
// 3.获取所有的构造方法,包括私有的
Constructor<?>[] con2 = clazz.getDeclaredConstructors();
for (Constructor<?> con : con2) {
System.out.println(con);
// private com.test.reflact.Student(java.lang.String,int)
// protected com.test.reflact.Student(int)
// public com.test.reflact.Student(java.lang.String)
// public com.test.reflact.Student()
}
System.out.println("=======================");
// 4.获取单个
Constructor<?> con3 = clazz.getDeclaredConstructor(); // public com.test.reflact.Student()
System.out.println(con3);
System.out.println("=======================");
Constructor<?> con4 = clazz.getDeclaredConstructor(String.class); // public com.test.reflact.Student(java.lang.String)
System.out.println(con4);
System.out.println("=======================");
// Constructor<?> con5 = clazz.getConstructor(int.class); // 因为是protected修饰的,所以必须用getDeclaredConstructor才能获取,不然报错Exception in thread "main" java.lang.NoSuchMethodException: com.test.reflact.Student.<init>(int)
Constructor<?> con6 = clazz.getDeclaredConstructor(int.class);
System.out.println(con6); // protected com.test.reflact.Student(int)
System.out.println("=======================");
Constructor<?> con7 = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(con7); // private com.test.reflact.Student(java.lang.String,int)
System.out.println("=======================");
// 构造方法获取到了以后,可以干很多事情。能获取到权限修饰符,所有的形参,能去创建对象。
// 这里以con7举例:
// 获取权限修饰符
int modifiers = con7.getModifiers();
System.out.println(modifiers); // 2,public返回的是1,在API帮助文档里面可以查到(搜索常量字段值)
// 获取方法的形参
Parameter[] parameters = con7.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter);
// java.lang.String arg0
// int arg1
}
//创建对象
// Student stu = (Student)con4.newInstance("张三", 23);
// System.out.println(stu); // 报错class com.test.reflact.Test cannot access a member of class com.test.reflact.Student with modifiers "private"
// 上面为什么会报错?
// 我们这里不是getDeclared了吗?还有一个细节:
// getDeclared他只是能让你看到这个构造方法,但是还不能直接利用这个构造去创建对象
// 如果你想用它去创建对象,在它的前面还需要调用一个方法叫做setAccessible(true)
// 他就表示临时取消权限的校验,此时就可以利用私有的构造方法去创建对象,这种方式也叫“暴力反射”
// 就是说Student这边虽然私有了,不让我去用,但是我直接暴力使用
con7.setAccessible(true);
Student stu = (Student)con7.newInstance("张三", 23);
System.out.println(stu); // Student{name='张三', age=23}
}
}
四:利用反射获取成员变量
Class类中用于获取成员变量的方法:
1.Field[] getFields(); 返回所有"公共"成员变量对象的数组
2.Field[] getDeclaredFields: 返回所有成员变量对象的数组
3.Field getField(String name); 返回单个公共成员变量对象
4.Field getDeclaredField(String name); 返回单个成员变量对象
Field类中用于创建对象的方法:
1.void set(Object obj, Object value): 赋值
2.Object get(Object obj): 获取值
public class Student {
private String name;
private int age;
public String gender;
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
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 class Test1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// 1.获取Class字节码文件对象
Class<?> clazz = Class.forName("com.test.reflect2.Student");
// 2.获取成员变量
Field[] fields1 = clazz.getFields();
for (Field field : fields1) {
System.out.println(field); // public java.lang.String com.test.reflect2.Student.gender
}
System.out.println("-----------------------");
// 3.获取成员变量(包括私有)
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field);
// private java.lang.String com.test.reflect2.Student.name
// private int com.test.reflect2.Student.age
// public java.lang.String com.test.reflect2.Student.gender
}
System.out.println("-----------------------");
// 4.获取单个成员变量
Field gender = clazz.getField("gender"); // public java.lang.String com.test.reflect2.Student.gender
System.out.println(gender);
System.out.println("-----------------------");
// Field name = clazz.getField("name"); // 报错,因为name是私有的
// System.out.println(name);
System.out.println("-----------------------");
Field name = clazz.getDeclaredField("name"); // private java.lang.String com.test.reflect2.Student.name
System.out.println(name);
System.out.println("-----------------------");
// 5.获取权限修饰符,下面均以name为例
int modifiers = name.getModifiers();
System.out.println(modifiers); // 2
// 6.获取成员变量名
String name1 = name.getName();
System.out.println(name1); // name
// 7.获取数据类型
Class<?> type = name.getType();
System.out.println(type); // class java.lang.String
// 获取成员变量的值,值和对象有关系,所以先创建对象
// Student student = new Student("张三", 23, "男");
// Object value = name.get(student); // 还是报错 class com.test.reflect2.Test1 cannot access a member of class com.test.reflect2.Student with modifiers "private"
// 如果想获得,得临时取消访问权限
Student student = new Student("张三", 23, "男");
name.setAccessible(true);
String value = (String)name.get(student);
System.out.println(value); // 张三
// 8.修改对象里面已经记录的值,也就是把student里面已经记录的张三改成李四
// 这个方法有两个参数:
// 第一个:要把哪个对象里面的值进行修改
// 第二个:要修改成什么
name.set(student, "李四");
System.out.println(student); // Student{name='李四', age=23, gender='男'}
}
}
五:获取成员方法
Class类中用于获取成员方法的方法:
1.Method[] getMethods(); 返回所有公共成员方法对象的数组,包括继承的
2.Method[] getDeclaredMethods(); 返回所有成员方法对象的数组,不包括继承的
3.Method getMethod(String name, Class<?>… parameterTypes): 返回单个公共成员方法对象
4.Method getDeclaredMethod(String name, Class<?> … parameterTypes); 返回单个成员方法对象
Method类中用于创建对象的方法:
1.Object invoke(Object obj, Object… args); 运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 void sleep() {
System.out.println("睡觉");
}
public void eat(String something) throws NullPointerException{
System.out.println("在吃" + something);
}
public void eat(String something, int age) throws NullPointerException{
System.out.println("在吃" + something);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 1.获取字节码文件对象
Class<?> clazz = Class.forName("com.test.reflect3.Student");
// 2.获取所有方法(包含父类中所有的公共方法)
Method[] methods = clazz.getMethods();
for (Method method : methods) {
// System.out.println(method); //...
}
System.out.println("====================");
// 3.获取所有方法,包括私有(不能获取父类的,但是可以获取本类中私有的方法)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
System.out.println("====================");
// 4.获取单个
// Method m = clazz.getMethod("eat", String.class);
// System.out.println(m); // 报错
// 5.获取单个(包括私有)
Method m2 = clazz.getDeclaredMethod("eat", String.class);
System.out.println(m2);
System.out.println("====================");
// 6.获取方法的修饰符
int modifiers = m2.getModifiers();
System.out.println(modifiers); // 2
// 7. 获取方法的名字
String name = m2.getName();
System.out.println(name); // eat
// 8.获取方法的形参
Parameter[] parameters = m2.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter); // java.lang.String arg0
}
// 9.获取方法抛出的异常
Class<?>[] exceptionTypes = m2.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(exceptionType); // class java.lang.NullPointerException
}
// 10.获取方法的返回值
Student student = new Student();
m2.setAccessible(true);
// 参数一:表示方法的调用者
// 参数二:表示在调用方法时传递的参数
m2.invoke(student, "坤坤");// 在吃坤坤
}
}
六:反射的作用
1.获取一个类里面所有的信息,获取到了之后,再执行其他的业务逻辑
2.结合配置文件,动态的创建对象并调用方法
七:反射练习题
1.保存信息
对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
public class MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException {
Student s = new Student("小A", 23, '女', 167.5, "睡觉");
Teacher t = new Teacher("播妞", 10000);
saveObject(t);
}
// 把对象里面所有的成员变量和值保存到本地文件中
private static void saveObject(Object obj) throws IllegalAccessException, IOException {
// 1. 获取字节码文件对象
Class<?> clazz = obj.getClass();
// 2.创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\a.txt"));
// 2. 获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// 获取成员变量的名字
String name = field.getName();
// 获取成员变量的值
Object value = field.get(obj);
bw.write(name + "=" + value);
bw.newLine();
}
bw.close(); // 记得关流喔
}
}
2.跟配置文件结合动态创建
反射可以跟配置文件结合的方式,动态的创建对象,并调用方法
prop.properties里面:
classname=com.test.reflectExercise2.Teacher
method=teach
public class MyReflectDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
// 1.读取配置文件中的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("C:\\Users\\IDEA\\IdeaProjects\\Eight-part essay interview\\Day01\\src\\main\\resources\\prop.properties");
prop.load(fis);
fis.close();
System.out.println(prop); // {classname=com.test.reflectExercise2.Student, method=study}
// 2.获取全类名和方法名
String className = (String) prop.get("classname");
String methodName = (String) prop.get("method");
System.out.println(className); // com.test.reflectExercise2.Student
System.out.println(methodName); // study
// 3. 利用反射创建对象并运行方法
Class<?> clazz = Class.forName(className);
Constructor<?> con = clazz.getDeclaredConstructor();
Object o = con.newInstance();
System.out.println(o); // Student{name='null', age=0}
// 4.获取成员方法并运行
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
method.invoke(o); // 学生在学习
}
}
八:总结
1.反射的作用?
(1)获取任意一个类中的所有信息
(2)结合配置文件动态创建对象
2.获得class字节码文件对象的三种方式
(1)Class.forName(“全类名”)
(2)类名.class
(3)对象.getClass();
3.如何获取构造方法、成员方法、成员变量
get:获取 set:设置
Constructor:构造方法 Parameter:参数
Field:成员变量 Modifiers:修饰符
Method:方法 Declared:私有的