反射
反射
认识一下什么是反射。其实API文档中对反射有详细的说明,我们去了解一下。在java.lang.reflect包中对反射的解释如下图所示
翻译成人话就是:反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(成员变量、方法、构造器等)。
反射有啥用呢?其实反射是用来写框架用的,但是现阶段对框架还没有太多感觉。为了方便理解,举个例子:平时我们用IDEA开发程序时,用对象调用方法,IDEA会有代码提示,idea会将这个对象能调用的方法都给你列举出来,供你选择,如果下图所示
问题是IDEA怎么知道这个对象有这些方法可以调用呢? 原因是对象能调用的方法全都来自于类,IDEA通过反射技术就可以获取到类中有哪些方法,并且把方法的名称以提示框的形式显示出来,所以你能看到这些提示了。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
因为反射获取的是类的信息,那么反射的第一步首先获取到类才行。由于Java的设计原则是万物皆对象,获取到的类其实也是以对象的形式体现的,叫字节码对象,用Class类来表示。获取到字节码对象之后,再通过字节码对象就可以获取到类的组成成分了,这些组成成分其实也是对象,其中每一个成员变量用Field类的对象来表示、每一个成员方法用Method类的对象来表示,每一个构造器用Constructor类的对象来表示。
如下图所示:
1.1 获取类的字节码
反射的第一步:是将字节码加载到内存,我们需要获取到的字节码对象。
比如有一个Student类,获取Student类的字节码代码有三种写法。不管用哪一种方式,获取到的字节码对象其实是同一个。
//获取字节码文件的方式
//第一种,类名.class
Class cls1 = Student.class;
System.out.println(cls1);
//第二种,通过创建好的对象去获取student.getClass()
Student student = new Student();
Class cls2 = student.getClass();
System.out.println(cls2);
//第三种,Class.forName();
Class<?> cls3 = Class.forName("day20230811.reflect.Student");
System.out.println(cls3);
//通过类加载器获取
ClassLoader loader = Student.class.getClassLoader();
Class<?> cls4 = loader.loadClass("day20230811.reflect.Student");
System.out.println(cls4);
public class Test1Class{
public static void main(String[] args){
Class c1 = Student.class;//第一种方式
System.out.println(c1.getName()); //获取全类名
System.out.println(c1.getSimpleName()); //获取简单类名
//第二种方式全类名
Class c2 = Class.forName("com.itheima.d2_reflect.Student");
System.out.println(c1 == c2); //true
Student s = new Student();
Class c3 = s.getClass();//第三种方式
System.out.println(c2 == c3); //true
}
}
1.2 获取类的构造器
获取到类的字节码对象之后。接下来,我们学习一下通过字节码对象获取构造器,并使用构造器创建对象。
获取构造器,需要用到Class类提供的几个方法,如下图所示:
方法 | 说明 |
---|---|
Constructor<?>[] getContructors() | 获取全部构造器 |
Constructor<?>[] getDeclaredConstructors() | 获取全部构造器(只要存在就能拿到) |
Constructor getConstructor(Class<?>… parameterTypes) | 获取某个构造器 (只能获取public修饰的) |
Constructor getDeclaredConstructor(Class<?>…parameterTypes) | 获取某个构造器 (只要存在就能拿到) |
按照规律来记就很方便了。
get:获取
Declared: 有这个单词表示可以获取任意一个,没有这个单词表示只能获取一个public修饰的
Constructor: 构造方法的意思
后缀s: 表示可以获取多个,没有后缀s只能获取一个
假设现在有一个Student类,里面有几个构造方法,代码如下
package com.study.day0811;
public class Student extends Person {
private int sno;
double height;
protected double weight;
public double socre;
public Student() {
}
public Student(double height, double weight) {
this.height = height;
this.weight = weight;
}
Student(int sno, double height, double weight, double socre) {
this.sno = sno;
this.height = height;
this.weight = weight;
this.socre = socre;
}
public void study() {
System.out.println("Student study");
}
public String showInformation(){
return "Student showInformation";
}
private String showInformation(String name){
return "Student showInformation" + name;
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", height=" + height +
", weight=" + weight +
", socre=" + socre +
'}';
}
}
-
- 接下来,我们来尝试获取类中所有的构造器
package com.study.day0811;
import java.lang.reflect.Constructor;
public class Demo2 {
public static void main(String[] args) throws NoSuchMethodException {
Class<Student> cls = Student.class;
//getConstructors只能获取到被public修饰的构造方法
System.out.println("getConstructors只能获取到被public修饰的构造方法");
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("---------------------------------");
//getDeclaredConstructors获取到所有的构造方法
System.out.println("getDeclaredConstructors获取到所有的构造方法");
Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
System.out.println("---------------------------------");
//获取无参的构造方法
System.out.println("获取无参的构造方法");
Constructor<Student> constructor = cls.getConstructor();
System.out.println(constructor);
//获取有参的构造方法
System.out.println("获取有参的构造方法");
Constructor<Student> constructor1 = cls.getConstructor(double.class, double.class);
System.out.println(constructor1);
}
}
运行测试方法打印结果如下
-
- 演示获取单个构造器试一试
//获取单个构造器
public static void test() throws NoSuchMethodException {
Class<Student> cls = Student.class;
//获取类public修饰的空参数构造器
Constructor constructor1 = cls.getConstructor();
System.out.println(constructor1.getName() + " 参数个数: " + constructor1.getParameterCount());
//获取private修饰的有两个参数(double,double)的构造器
Constructor constructor2 = cls.getDeclaredConstructor(double.class,double.class);
System.out.println(constructor2.getName() + " 参数个数: " + constructor2.getParameterCount());
}
打印结果如下
1.3 反射获取构造器的作用
上一节我们已经获取到了Student类中的构造器。获取到构造器后,有什么作用呢?
其实构造器的作用:初始化对象并返回。
这里我们需要用到如下的两个方法,注意:这两个方法时属于Constructor的,需要用Constructor对象来调用。
Constructor提供的方法 | 说明 |
---|---|
T newInstance(Object… initargs) | 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回 |
public void setAccessible(boolean flag) | 设置为true,表示禁止检查访问控制(暴力反射) |
如下图所示,constructor1和constructor2分别表示Student类中的两个构造器。现在我要把这两个构造器执行起来
获得的构造器如下:
由于构造器是private修饰的,先需要调用setAccessible(true)
表示禁止检查访问控制,然后再调用newInstance(实参列表)
就可以执行构造器,完成对象的初始化了。
//获取单个构造器
public static void test() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Student> cls = Student.class;
//获取类public修饰的空参数构造器
Constructor constructor1 = cls.getConstructor();
System.out.println(constructor1.getName() + " 参数个数: " + constructor1.getParameterCount());
constructor1.setAccessible(true);//表示禁止检查访问控制
Student s1 = (Student) constructor1.newInstance();//执行构造器,完成对象的初始化。
System.out.println(s1);
System.out.println("-----------------------------------------");
//获取private修饰的有两个参数(double,double)的构造器
Constructor constructor2 = cls.getDeclaredConstructor(double.class,double.class);
System.out.println(constructor2.getName() + " 参数个数: " + constructor2.getParameterCount());
constructor2.setAccessible(true);//表示禁止检查访问控制
Student s2 = (Student) constructor2.newInstance(175,65);//执行构造器,完成对象的初始化。
System.out.println(s2);
}
代码的执行结果如下:
1.4 反射获取成员变量&使用
同学们,上一节我们已经学习了获取类的构造方法并使用。接下来,我们再学习获取类的成员变量,并使用。
其实套路是一样的,在Class类中提供了获取成员变量的方法,如下图所示。
方法 | 说明 |
---|---|
public Field[] getFields() | 获取类的全部成员变量(只能获取public修饰的) |
public Field[] getDeclaredFields() | 获取类的全部成员变量(只要存在就能拿到) |
public Field getField(String name) | 获取类的某个成员变量(只能获取public修饰的) |
public Field getDeclaredField(String name) | 获取类的某个成员变量 (只要存在就能拿到) |
这些方法的记忆规则,如下
get:获取
Declared: 有这个单词表示可以获取任意一个,没有这个单词表示只能获取一个public修饰的
Field: 成员变量的意思
后缀s: 表示可以获取多个,没有后缀s只能获取一个
- 用Class类提供 的方法将成员变量的对象获取出来。
Class<Student> cls = Student.class;
//getFields只能获取被public修饰的类的全部成员变量,包括本类的和父类的
System.out.println("getFields只能获取被public修饰的类的全部成员变量,包括本类的和父类的");
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("----------------------");
//getDeclaredFields获取所有的属性
System.out.println("getDeclaredFields获取所有的属性");
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("------------------------");
执行完上面的代码之后,我们可以看到控制台上打印输出了,每一个成员变量的名称和它的类型。
- 获取到成员变量的对象之后该如何使用呢?
在Filed类中提供给给成员变量赋值和获取值的方法,如下图所示。
方法 | 说明 |
---|---|
void set(Object obj,object value): | 赋值 |
Object get(Object obj) | 取值 |
public void setAccessible(boolean flag) | 设置为true,表示禁止检查访问控制(暴力反射) |
再次强调一下设置值、获取值的方法时Filed类的需要用Filed类的对象来调用,而且不管是设置值、还是获取值,都需要依赖于该变量所属的对象。代码如下
System.out.println("------------------------");
System.out.println("获取某个成员变量(只要存在就能拿到)");
Field sno = cls.getDeclaredField("sno");
//获取属性名称
System.out.println(sno.getName());
//获取属性的类型
Class<?> type = sno.getType();
String name = type.getName();
System.out.println(name);
//获取修饰符
int modifiers = sno.getModifiers();
System.out.println(Modifier.toString(modifiers));
System.out.println("--------------------");
Student student = cls.newInstance();
//通过set方法为属性赋值
sno.setAccessible(true);
sno.set(student,12);
System.out.println(student);
执行代码,控制台会有如下的打印
1.5 反射获取成员方法
各位同学,上面几节我们已经学习了反射获取构造方法、反射获取成员变量,还剩下最后一个就是反射获取成员方法并使用了。
在Java中反射包中,每一个成员方法用Method对象来表示,通过Class类提供的方法可以获取类中的成员方法对象。如下下图所示
方法 | 说明 |
---|---|
Method[] getMethods() | 获取类的全部成员方法(只能获取public修饰的 |
Method[] getDeclaredMethods() | 获取类的全部成员方法(只要存在就能拿到) |
Method getMethod(string name,Class<… parameterTypes) | 获取类的某个成员方法(只能获取public修饰的) |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 获取类的某个成员方法(只要存在就能拿到) |
//只能获取被public修饰的方法
System.out.println("只能获取被public修饰的方法");
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("----------------------------");
获得所有被public修饰的方法
//获取所有的方法
System.out.println("获取所有的方法");
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
//获取方法名
System.out.println("获取方法名:" + declaredMethod.getName());
//获取方法的返回值类型
System.out.println("获取方法的返回值类型:" + declaredMethod.getReturnType());
//获取方法的修饰符
System.out.println("获取方法的修饰符:" + Modifier.toString(declaredMethod.getModifiers()));
//获取方法的参数列表
System.out.println("获取方法的参数列表:" + Arrays.toString(declaredMethod.getParameterTypes()));
System.out.println(declaredMethod);
System.out.println("===============================");
}
打印输出每一个成员方法的名称、参数格式、返回值类型,运行结果
也能获取单个指定的成员方法,如下图所示
获取到成员方法之后,有什么作用呢?
在Method类中提供了方法,可以将方法自己执行起来。
Method提供的方法 | 说明 |
---|---|
public Object invoke(Object obj,Object… args) | 触发某个对象的该方法执行。 |
public void setAccessible(boolean flag) | 设置为true,表示禁止检查访问控制(暴力反射) |
新建一个Dog类演示一下,把run()
方法和eat(String name)
方法执行起来。看分割线之下的代码
public class DogDemo {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//反射第一步:先获取到Class对象
Class<Dog> dogClass = Dog.class;
//获取类中的全部方法
Method[] methods = dogClass.getDeclaredMethods();
for (Method m: methods) {
System.out.println("方法名--" +m.getName() + ",参数数--" + m.getParameterCount() + ",返回值--" + m.getReturnType());
}
System.out.println("------------------------------");
//获取private修饰的run方法,得到Method对象
Method run = dogClass.getDeclaredMethod("run", String.class);
//执行run方法,在执行前需要取消权限检查
Dog dog = new Dog("富贵");
run.setAccessible(true);
Object r = run.invoke(dog,"富贵");
System.out.println(r);
//获取private 修饰的eat(String name)方法,得到Method对象
Method eat = dogClass.getDeclaredMethod("eat");
eat.setAccessible(true);
Object e = eat.invoke(dog);
System.out.println(e);
}
}
打印结果如下图所示:run()方法执行后打印修勾run:富贵摇着yi巴跑过来
,返回null
; eat()方法执行完,直接返回修勾eat:富贵啃骨头
反射:框架设计的灵魂
* 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
* 反射:将类的各个组成部分封装为其他对象,这就是反射机制
* 好处:
1. 可以在程序运行过程中,操作这些对象。
2. 可以解耦,提高程序的可扩展性。
* 获取Class对象的方式:
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
* 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
2. 类名.class:通过类名的属性class获取
* 多用于参数的传递
3. 对象.getClass():getClass()方法在Object类中定义着。
* 多用于对象的获取字节码的方式
* 结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
* Class对象功能:
* 获取功能:
1. 获取成员变量们
* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name)
2. 获取构造方法们
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
3. 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4. 获取全类名
* String getName()
* Field:成员变量
* 操作:
1. 设置值
* void set(Object obj, Object value)
2. 获取值
* get(Object obj)
3. 忽略访问权限修饰符的安全检查
* setAccessible(true):暴力反射
* Constructor:构造方法
* 创建对象:
* T newInstance(Object... initargs)
* 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
* Method:方法对象
* 执行方法:
* Object invoke(Object obj, Object... args)
* 获取方法名称:
* String getName:获取方法名
* 案例:
* 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
* 实现:
1. 配置文件
2. 反射
* 步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法