1.什么是反射?
反射允许对成员变量,成员方法和构造方法的信息进行编程访问
就好像反射是个人,把类中的字段(成员方法),构造方法,成员方法挨个获取出来
比如idea自动提示功能就是用的反射来实现的,创建了一个对象,用对象去调用方法,成员变量,构造方法...等,idea就会利用反射把里面能调用的成员方法等获取出来,进行展示
像以前创建对象或者调用方法的时候,我们可以按ctrl+p 提示,就是通过反射实现的
说白了就是反射可以从类中拿东西,常用的有:成员变量,构造方法,成员方法等所有的信息
比如:
获取 解刨
字段(成员变量): 获取修饰符 获取名字 获取数据类型 赋值/获取已经记录的值
构造方法: 获取修饰符 获取名字 获取形参 还可以利用获取出来的构造方法去创建对象
成员方法: 获取修饰符 获取名字 获取形参 获取返回值 抛出的异常 获取注解 运行方法
注意:在获取的时候不是从java文件中获取的,而是从Class字节码文件当中获取的,首先要先获取Class字节码文件对象,再从字节码文件去获取字段(成员变量),构造方法,,成员方法
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
反射它也是框架设计的灵魂。什么是框架: 它是一个别人设计好的一个半成品,在使用时只要填入自己的业务代码。好处: 提高开发效率。
反射: 在运行时,它是把类中成员抽取为其他类对象的过程。
2.获取Class对象的三种方式
- Class.forName("全类名");
- 类名.Class
- 对象.getClass();如果有了这个类对象,用对象去调用getClass,而getClass是定义在Object中的,所有的对象都可以调用这个方法
有三种方式什么时候用哪一种呢?
第一个阶段:编写java文件
A.java---编译成class文件 A.class,在这个阶段是没有把代码加载到内存当中的,是在硬盘/磁盘里进行的操作 也称为源代码阶段 一般在这个阶段会用第一种方式去获取Class字节码对象
package com.demo.a3_fanShe;
public class Student2 {
private String name;
private Integer age;
public Student2() {
}
public Student2(String name, Integer age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public Integer getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "Student2{name = " + name + ", age = " + age + "}";
}
}
而在我们输入包名和类名的时候,如果怕输错,可以直接去类名右键,按着下方操作就能复制全类名
package com.demo.a3_fanShe;
public class Demo2 {
public static void main(String[] args) throws ClassNotFoundException {
/*
获取Class对象的三种方式:
1. Class.forName("全类名");
2. 类名.Class
3. 对象.getClass();如果有了这个类对象,用对象去调用getClass,而getClass是定义在Object中的,所有的对象都可以调用这个方法
* */
//1.第一种方式
//全类名: 包名 + 类名 com.demo.a3_fanshe.Student2
Class clazz1 = Class.forName("com.demo.a3_fanShe.Student2");
//打印
System.out.println(clazz);
}
}
结果输出如下:
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=64711:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\java-gaojie\target\classes com.demo.a3_fanShe.Demo2
class com.demo.a3_fanShe.Student2
Process finished with exit code 0
第二个阶段:运行代码.
要把字节码文件A.calss 加载到内存当中,称为加载阶段,一般用第二种方式
package com.demo.a3_fanShe;
public class Demo2 {
public static void main(String[] args) throws ClassNotFoundException {
/*
获取Class对象的三种方式:
1. Class.forName("全类名");
2. 类名.Class
3. 对象.getClass();如果有了这个类对象,用对象去调用getClass,而getClass是定义在Object中的,所有的对象都可以调用这个方法
* */
//1.第一种方式
//全类名: 包名 + 类名 com.demo.a3_fanshe.Student2
Class clazz1 = Class.forName("com.demo.a3_fanShe.Student2");
//2.第二种方式
Class clazz2= Student2.class;
System.out.println(clazz2);
}
}
打印结果:
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=64913:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\java-gaojie\target\classes com.demo.a3_fanShe.Demo2
class com.demo.a3_fanShe.Student2
Process finished with exit code 0
第二种获取的字节码文件和第一种是一样的对象,可以看下面的打印结果
第三个阶段:创建对象
我们可以在这个类的当中去创建对象.比如 A a = new A();此时就叫做运行阶段,一般用第三种方式,
用对象去调用getClass方法也可以获取到字节码对象
package com.demo.a3_fanShe;
public class Demo2 {
public static void main(String[] args) throws ClassNotFoundException {
/*
获取Class对象的三种方式:
1. Class.forName("全类名");
2. 类名.Class
3. 对象.getClass();如果有了这个类对象,用对象去调用getClass,而getClass是定义在Object中的,所有的对象都可以调用这个方法
* */
//1.第一种方式
//全类名: 包名 + 类名 com.demo.a3_fanshe.Student2
Class clazz1 = Class.forName("com.demo.a3_fanShe.Student2");
//2.第二种方式
Class clazz2= Student2.class;
//3.第三种方式
Student2 s =new Student2();
Class clazz3 = s.getClass();
System.out.println(clazz3);
}
}
结果输出如下:跟上面两个是一样的
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=65018:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\java-gaojie\target\classes com.demo.a3_fanShe.Demo2
class com.demo.a3_fanShe.Student2
Process finished with exit code 0
从上面的分析我们知道,任意一个字节码都会通过ClassLoader类加载器,加载到JVM内存中,并以Class类对象的形式存在。注意:一个类的字节码只会被加载一次。
那什么时候该用哪种呢?
第一种方式最为常用,第二种一般更多的是当做参数进行传递,第三种当我们已经有了这个类的对象时,才可以使用。
在java中万物皆可为对象
字节码 可以看成 Class 对象
构造方法 Constructor 对象
字段(成员变量) Field 对象
成员方法 Method对象
3.通过反射类获取类对象
通过Class类对象,调用newInstance() 为了方便阅读我们放到一个类中
package com.demo.a3_fanShe.demo3;
public class Demo3 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//用第一种方式 获取Class
Class<Student> clazz= (Class<Student>) Class.forName("com.demo.a3_fanShe.demo3.Student");
//通过反射类得到对应的类对象---之前方式:new Student来获取Student类的对象。
Student student1 = clazz.newInstance();
Student student2 = clazz.newInstance();
System.out.println(student1);
System.out.println(student2);
}
}
class Student {
private String name;
private Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public Integer getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(Integer age) {
this.age = age;
}
public String toString() {
return "Student3{name = " + name + ", age = " + age + "}";
}
}
打印结果如下
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=52854:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\java-gaojie\target\classes com.demo.a3_fanShe.demo3.Demo3
Student3{name = null, age = null}
Student3{name = null, age = null}
Process finished with exit code 0
注意:如果没加泛型,对象就是Object
像这些都有反射
spring---spring.xml====><bean class="com.aaa.service.UserService"/>
--spring根据你传递的类路径----得到该类的反射类---并通过newInstance()创建类对象。
mybatis--=====> sqlSession.getMapper(UserDao.class);---->UserDao.class反射类---->mybatis创建UserDao接口的类对象。
4.利用反射获取构造方法
Class中用于获取构造方法的方法
Constructor<?>[ ] getConstructors(): 返回所有公共(public)构造方法对象的数组
Constructor<?>[ ] getDeclaredConstructors():返回所有构造方法对象,包括私有
Constructor<T>getConstructor(Class<?>...parameterTypes):返回单个公共(public)构造方法对象
Constructor<T>getDeclaredConstructor(Class<?>...parameterTypes):返回单个构造方法对象,包括私有
Constructor类中用于创建对象的方法
T newInstance(Object...initargs): 根据指定的构造方法创建对象
setAccessible(boolean flag): 设置为true,表示取消访问检查
package com.demo.a3_fanShe.demo4;
public class Student {
private String name;
private int age;
public Student(){
}
public Student(String name){
this.name=name;
}
protected Student(int age){
this.age=age;
}
private Student(String name,int age){
this.name=name;
this.age=age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
package com.demo.a3_fanShe.demo4;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
public class Demo4 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
/*
Class中用于获取构造方法的方法
Constructor<?>[ ] getConstructors(): 返回所有公共(public)构造方法对象的数组
Constructor<?>[ ] getDeclaredConstructors():返回所有构造方法对象,包括私有
Constructor<T>getConstructor(Class<?>...parameterTypes):返回单个公共(public)构造方法对象
Constructor<T>getDeclaredConstructor(Class<?>...parameterTypes):返回单个构造方法对象,包括私有
Constructor类中用于创建对象的方法
T newInstance(Object...initargs): 根据指定的构造方法创建对象
setAccessible(boolean flag): 设置为true,表示取消访问检查
*/
//1.获取Class字节码文件对象
Class<Student> clazz = (Class<Student>) Class.forName("com.demo.a3_fanShe.demo4.Student");
//2.获取所有公共的构造方法
Constructor[] cons= clazz.getConstructors();
for (Constructor con : cons) {
}
//3.获取所有构造方法,包括私有的
Constructor[] cons2= clazz.getDeclaredConstructors();
for (Constructor con2 : cons2) {
}
//4.获取单个公共的空参构造对象 不加类型.class
Constructor cons3 = clazz.getDeclaredConstructor();
//5.获取单个公共的有参构造对象 加上对应的类型.class
Constructor cons4 = clazz.getDeclaredConstructor(String.class);
//6.获取受保护的有参构造 加上对应的类型.class
Constructor cons5 = clazz.getDeclaredConstructor(int.class);
//7.获取两个参数的 加上对应的类型.class
Constructor cons6 = clazz.getDeclaredConstructor(String.class, int.class);
//8.获取权限修饰符
int modifiers = cons6.getModifiers();
//9.获取到构造方法所有的参数
Parameter[] parameters = cons6.getParameters();
for (Parameter parameter : parameters) {
}
//10.也可以创建对象
//表示临时取消权限的校验,也称为暴力反射
cons6.setAccessible(true);
Student student = (Student) cons6.newInstance("小丽", 15);
System.out.println(student);
}
}
第一种:获取所有公共的构造方法
第二种:获取所有构造方法,包括私有的...等
第三种:获取单个公开的空参构造对象,不加类型.class
第四种:获取公共有参构造 ,获取的类型要和构造参数里的数据类型保持一致
第五种:获取单个私有的有参构造,注意!这样会报错的,要把getConstuotor前加上Declared,因为不加只能获取公共的,获取不到私有的和受保护的
第六种:获取私有,有参构造,两个参数的 加上对应的类型
第七种:获取权限修饰符
这里的public 是1 是底层的运行效率 有关系的 详细可以去看看 原码 补码 反码
权限修饰符获取到有什么用呢?
底层会获取到权限修饰符,像下面的能获取到 空参的,有参的,但是没有两个参数的,因为是私有的
第八种:获取到构造方法所有的参数
有什么用呢?
这样就可以图形化看到提示的参数了
第九种:也可以创建对象,但是要注意,getDeclaredConstructor只是能让你看到私有的构造,但是还不能用,要想用还需要在前面加上setAccessible(true); 称为暴力反射
5.获取反射类中的成员变量
Class类中用于获取成员变量的方法
Field[ ] getFields(); 返回所有公共(public)成员变量对象的数组,包括继承的
Field[ ] getDeclaredFields(); 返回所有成员变量对象,包括私有,
Field getField(String name); 返回单个公共(public)成员变量对象,包括继承的
Field getDeclaredField(Sting name); 返回单个成员变量对象,包括私有
Field类中用于创建对象的方法
void set(Object obj,Object value ): 赋值
Object get(Object obj)获取值
package com.demo.a3_fanShe.demo5;
public class Student {
private String name;
private int age;
public String gender;
public Student() {
}
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return gender
*/
public String getGender() {
return gender;
}
/**
* 设置
* @param gender
*/
public void setGender(String gender) {
this.gender = gender;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + "}";
}
}
package com.demo.a3_fanShe.demo5;
import java.lang.reflect.Field;
public class Demo5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
/*
Class类中用于获取成员变量的方法
Field[ ] getFields(); 返回所有公共(public)成员变量对象的数组,包括继承的
Field[ ] getDeclaredFields(); 返回所有成员变量对象,包括私有,
Field getField(String name); 返回单个公共(public)成员变量对象,包括继承的
Field getDeclaredField(Sting name); 返回单个成员变量对象,包括私有
Field类中用于创建对象的方法
void set(Object obj,Object value ): 赋值
Object get(Object obj)获取值
*/
//1.获取Class字节码文件的对象
Class clazz = Class.forName("com.demo.a3_fanShe.demo5.Student");
//2.获取所有公共的成员变量
Field[] fields = clazz.getFields();
for (Field field : fields) {
}
//3.获取所有成员变量包括私有
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
}
//4.获取单个的成员变量
Field gender = clazz.getField("gender");
//5.获取单个私有的成员变量
Field name = clazz.getDeclaredField("name");
//6.获取成员变量的权限修饰符
int modifiers = name.getModifiers();
//7.获取成员变量名字
String n = name.getName();
//8.获取成员变量的数据类型
Class<?> type = name.getType();
System.out.println(type);
//9.获取成员变量记录得值
Student s = new Student("张三",18,"日本");
name.setAccessible(true);
String value = (String) name.get(s);
//10.修改对象里面记录的值
name.set(s,"李四");
System.out.println(s);
}
}
第一种:获取所有公共的成员变量
第二种:获取所有成员变量包括私有
第三种:获取单个公共的成员变量
这里需要的参数需要加上成员变量的名字
第四种:获取单个私有的成员变量
,这里需要的参数需要加上成员变量的名字,注意!这里也需要加上Declared,因为不加只能获取公共的,获取不到私有的和受保护的
第五种:获取成员变量的修饰符
第六种:获取成员变量的名字
第七种:获取成员变量的数据类型
第八种:获取成员变量记录的值
获取变量的值需要先创建一个对象,但是要注意:因为访问的是私有的,需要暴力反射
打印一个是因为通过一个name成员变量获得的
第九种:修改对象里面记录的值
第一个参数表示要把里面哪个对象里面的值修改,第二个参数你要修改成什么
6. 获取成员方法对象
Class类中用于获取成员方法的方法
Method[ ]getMethods(); 返回所有公共(public)成员方法对象的数组,包括继承的
Method[ ]getDeclaredMethods(); 返回所有成员方法对象的数组,不包括继承的,包括私有
Method getMethod(Sting name,Class<?>...parameterTypes): 返回单个(public)公共成员方法对象,包括继承的
Method getDeclaredMethod(Sting name,Class<?>...parameterTypes): 返回单个成员方法对象,包括私有
Method类中用于创建对象的方法
Object invoke(Object obj,Object...args): 运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
获取方法的修饰符
获取方法的名字
获取方法的形参
获取方法的返回值
获取方法的抛出的异常
package com.demo.a3_fanShe.demo6;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public void sleep(){
System.out.println("睡觉");
}
private void eat(String something){
System.out.println("在吃"+ something);
}
private void eat(String something,int a){
System.out.println("在吃"+ something);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
package com.demo.a3_fanShe.demo6;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class Demo6 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
/*
Class类中用于获取成员方法的方法
Method[ ]getMethods(); 返回所有公共(public)成员方法对象的数组,包括继承的
Method[ ]getDeclaredMethods(); 返回所有成员方法对象的数组,不包括继承的,包括私有
Method getMethod(Sting name,Class<?>...parameterTypes): 返回单个(public)公共成员方法对象,包括继承的
Method getDeclaredMethod(Sting name,Class<?>...parameterTypes): 返回单个成员方法对象,包括私有
Method类中用于创建对象的方法
Object invoke(Object obj,Object...args): 运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
获取方法的修饰符
获取方法的名字
获取方法的形参
获取方法的返回值
获取方法的抛出的异常
*/
//1. 获取Class字节码文件对象
Class clazz = Class.forName("com.demo.a3_fanShe.demo6.Student");
//2.获取里面所有公共的方法对象(包含父类中所有的方法)
Method[] methods = clazz.getMethods();
for (Method method : methods) {
/* System.out.println(method);*/
}
//3.获取子类/本类中所有的方法(不能获取父类的,但是可以获取本类的私有方法)
Method[] methods2 = clazz.getDeclaredMethods();
for (Method method : methods2) {
}
//4.获取指定的单一方法
Method eat = clazz.getDeclaredMethod("eat", String.class);
//5.获取方法的修饰符
int modifiers = eat.getModifiers();
//6.获取方法的名字
String name = eat.getName();
//7.获取方法的形参(把参数的对象获取到,并放到数组中)
Parameter[] parameters = eat.getParameters();
for (Parameter parameter : parameters) {
}
//8.获取方法的抛出的异常
Class[] exceptionTypes = eat.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
}
//9.方法运行
/*
Method类中用于创建对象的方法
Object invoke(Object obj,Object...args): 运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
*/
Student s=new Student();
eat.setAccessible(true);
//参数一s:表示方法的调用者
//参数二:"汉堡包"表示在调用方法的时候传递的实际参数
String res = (String) eat.invoke(s, "汉堡包");
System.out.println(res);
}
}
第一种:获取里面所有公共的方法对象
注意:(包含父类中所有的方法,它的父类是object)
第二种:获取子类/本类中所有的方法包括私有
第三种:获取指定的单个的方法
第一个参数表示: 方法的名字 第二个:参数的类型 是为了方法的重载,因为如果有两个
,它也不知道获取的是第一个eat,还是第二个
获取第一个指定的eat
获取第二个指定的eat
注意:要获得私有还是要加Declared
第四种:获取方法的修饰符
第五种:获取方法的名字
第五种:获取方法的形参(把参数的对象放到一个数组中)
第五种:获取方法抛出的异常
第六种:方法运行
还能获取返回值