浅谈反射
- 前言:
- 什么是反射
- 定义
- 解释
- 为什么要反射(反射的作用)
- 怎么反射
- 1获取Class对象
- 获取属性
- 1,先获取属性名
- 找指定对象对应该属性的属性值(涉及暴力反射)
- 获取构造方法
- 1,先获取构造器
- 创建对象(暴力反射)
- 获取方法(这个比较特殊)
- 获取方法
- 执行方法
- 反射案例(解释为什么反射是框架的灵魂)
- 反射案例解释
- 初步思考
- 第一步 创建配置文件
- 第二步 加载配置文件
- 第三步反射
- 第四步运行
- 换其他类的方法
前言:
今天简单学习了下反射,B站弹幕都说听不懂,我研究了一下,发现听不懂的原因在于内容有点多,但是又无法分开学,另外一点就是老师讲的有点抽象,所以借晚上总结的时候用容易理解的语言解释一下,加深记忆。
什么是反射
定义
反射是指在程序运行过程中,动态的获取类各部分的一种技术。详细一点就是将类的各个部分封装为其他的对象。
解释
| 这两句话比较抽象,需要仔细分析一下,举一个例子:我们定义一个学生类Student,类中主要的部分是 属性,构造方法,普通方法。当然也有toString。这里我定义的时候 属性,构造方法,普通方法都用了不同访问修饰符,大家稍微注意一下,后面会用到,代码简单看一下就行,直接看后面文章
package cn.itcast.person;
/**
* 学生类
*/
public class Student {
//属性
public String name;
protected int age;
String sex;
private int height;
/**
* 构造方法
*/
public Student() {
}
public Student(String name, int age, String sex, int height) {
this.name = name;
this.age = age;
this.sex = sex;
this.height = height;
}
private Student(String name){
this.name = name;
}
//普通方法
public void study() {
System.out.println("study");
}
private void sleep(){
System.out.println("sleep");
}
public void eat(int i ){
System.out.println("吃了"+i+"碗饭");
}
//get/set方法
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", height=" + height +
'}';
}
}
| 然后我们可以定义一个测试类,来创建学生对象并且调用方法。
package cn.itcast.person;
/**
* 学生测试类
*/
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
s.study();
}
}
我们平时就是这样写代码的,其实在这两步之间还有一步,就是将我们写的类加载到内存中,是虚拟机自动完成的,这一步就是大部分人不明白的地方,我们写的文件是保存为.java.类型的文件,编译完就是.class类型的文件,然后通过类加载器,将整个类变成一个Class类型对象,如下图
可以这样想象:有一个Class类,这个类中有各种类型的属性,如下
public class Class1 {//写Class1是应为class是关键字,
public Field[] shuxing ;//就是属性
public Constructor[] gouzaofangfa;//构造方方法
public Method[] fangfa;//普通方法
//构造方法省略。。。
//普通方法省略。。。
}
然后创建这个类的对象studentclass,这个对象就是学生类对应的class对象,什么意思呢,
这个对象有一个field数组类型的属性: public Field[] shuxing;
这个数组中放了学生类中所有的属性,也即是说,学生类的所有的属性(姓名,年龄,性别)在class对象中,都是field类型的!!
也有一个constructor数组类型的属性: public Constructor[] gouzaofangfa;
这个数组中放了所有学生类的构造方法,也即是说,学生类的所有的构造方法,在class对象中,都是constructor类型的!!
还有一个method数组类型的属性public Method[] fangfa;
这个数组放了学生类所有的普通方法,同样地,方法在class对象中都是method类型的
注意,这么说是方便理解,其实并不是这样存放的,但是不这样说,这段就很难理解
这段不理解不要紧,看完如何反射在回来看可以更方便理解。
为什么要反射(反射的作用)
动态获取类的各个组成部分,框架设计的灵魂。后面会用一个案例解释为啥是灵魂
怎么反射
1获取Class对象
有三种方法,分别对应类运行的三个过程
| 1,Class.forName(“全路径名”) 对应类刚写完,还没加载进内存
| 2,类名.class 对应加载完,还没生成对象,这是作为类的一个属性进行调用
| 3,对象.getclass();生成对象了,用对象调用方法,代码如下
public class ReflectTest1 {
public static void main(String[] args) throws Exception {
/**
* 获取类对应的class对象
* 1,Class.forName()
* 2,类名.class
* 3,对象.getclass()
*/
//1,Class.forName()
Class sclass1 = Class.forName("cn.itcast.person.Student");
//2,类名.class
Class sclass2 = Student.class;
// 3,对象.getclass(),先创建个学生类的对象
Student s = new Student("郭德纲", 45, "男", 170);
Class sclass3 = s.getClass();
System.out.println(sclass1);
System.out.println(sclass2);
System.out.println(sclass3);
System.out.println(sclass1 == sclass2);
System.out.println(sclass1 == sclass3);
}
这样我们就获取到了Student类对应的class对象,这里一定要注意,这三个对象是同一个,在内存中其实只有一个,上面5个输出分别是:
获取到class对象有啥用呢?我们还是没明白反射是怎么在程序运行时动态获取类的各个部分的,要知道怎么获取,首先要知道类有几个主要部分?三个,属性,构造方法,普通方法,当然也有类名啊,包名啊等等,我们说这三个主要的部分,
获取属性
| 获取属性就是要获取某个学生对象的属性值,分两步走:
1,先获取属性名
| 4种方法,以代码形式给出:
public class ReflectTest1 {
public static void main(String[] args) throws Exception {
/**
* 首先获取学生类的class对象
*/
Student s = new Student("郭德纲", 45, "男", 170);
Class sclass3 = s.getClass();
/**
* 获取属性,用class对象调对用的方法
* 1获取指定public的属性 getFiled()
* 2获取全部public的属性 getFileds()
*
* 3获取指定声明的属性 getDeclaredField()
* 4获取全部声明的属性 getDeclaredFields()
*/
/*1获取指定public的属性 getFiled(),除了name,
其他三个会报错,因为不是public修饰的
*/
Field name = sclass1.getField("name");
Field age = sclass1.getField("age");
Field sex = sclass1.getField("sex");
Field height = sclass1.getField("height");
//2获取全部public的属性 getFileds()
Field[] fields = sclass1.getFields();
//3获取指定声明的属性 getDeclaredField()
Field name1 = sclass1.getDeclaredField("name");
Field age1 = sclass1.getDeclaredField("age");
Field sex1 = sclass1.getDeclaredField("sex");
Field height1 = sclass1.getDeclaredField("height");
//4获取全部声明的属性 getDeclaredFields()
Field[] declaredFields = sclass1.getDeclaredFields();
解释:
getField(“属性名”)是获取指定的public修饰的属性名,不是Public修饰的不行,上面代码除了名字是public,另外三个会报错,
getFields();这个是获取所有的public修饰的属性名,返回的是一个数组,可以遍历出来。
declared意思是声明,就是学生类中有的,不论是不是私有,只要你在学生类,都能获取,所以getdeclaredField(“属性名”),可以获取私有的属性名,带s的也是返回一个数组,大家可以将他们都输出来试一下。
找指定对象对应该属性的属性值(涉及暴力反射)
我们找到了属性名,就要找某个学生对象 对应 该属性名的属性值,用获取到的属性名,调用get(对象名)方法,比如要获取姓名,
/**
* 获取属性值
*/
Object o = name.get(s);
System.out.println(o);
//对于私有的要用暴力反射,
age1.setAccessible(true);//忽略访问权限修饰符
Object o1 = age1.get(s);
System.out.println(o1);
控制台输出:
对于私有的,用带declared的获取到后,要加一行代码
属性名.setAccessible(true);
这样可以忽视访问权限修饰符,称为暴力反射,简称暴射。
这里就有疑问了不是带declared能获取到的私有的吗,要注意刚才获取到的是属性名,现在是找属性值,私有的属性值要暴力反射。就比如,体重是一个属性名,但是我们不能直接问女朋友你多少斤,人家的具体体重(属性值),是私有的。
大家可以尝试一输出另外的几个方法获取到的属性值。
获取构造方法
获取构造方法就是要用来创建对象
1,先获取构造器
| 4种,代码如下
*/
//获取指定的public修饰的构造方法,参数可以这样写,没有参数就不写
Constructor constructor1 = sclass1.getConstructor(String.class, int.class, String.class, int.class);
//获取指定的声明过的构造方法(可以是私有的)
Constructor declaredConstructor = sclass1.getDeclaredConstructor(String.class);
同样,带s的是获取全部,并且返回数组,大家可以试一下
创建对象(暴力反射)
获取到构造方法,就要创建对象了,用newInstance();方法,参数列表写想传入的参数,我用的第二个,是私有的所以用暴力反射
declaredConstructor.setAccessible(true);
Object zzy = declaredConstructor.newInstance("zzy");
System.out.println(zzy);
控制台输出:因为只传了名字,所以其他为空
大家可以试一下其他的几个方法,并且输出一下
获取方法(这个比较特殊)
获取方法目的就是要执行该方法
获取方法
| 4种,代码如下
* 获取方法
* 1获取指定名称的public方法
* 2获取全部的public方法(包括继承过来的)
*
* 3获取指定名称的 该类声明过的方法(继承不算,实现接口的算)
* 4获取全部声明的方法
*/
Method study = sclass1.getMethod("study");
Method[] methods = sclass1.getMethods();
Method sleep = sclass1.getDeclaredMethod("sleep");
Method[] declaredMethods = sclass1.getDeclaredMethods();
大家获取之后输出一下,会发现不带declared的那两个,输出了一大堆,这是因为,所有的类都继承Object类,继承过来的方法,也是默认有的,只不过我们看不见。
但是带declared的没有,因为declared的意思是声明的,学生类继承的Object类中的方法并没有在该类中声明,所以没有,但是,实现接口中的方法是有声明的,会存在。
从这里也不难看出,declared这个声明是什么意思,就是写出来的,看得到的,不论你什么修饰符,写出来就能获取,没写出来的就不获取
执行方法
用获取到的方法对象调用invokes()方法
Method study = sclass1.getMethod("study");
Method[] methods = sclass1.getMethods();
Method sleep = sclass1.getDeclaredMethod("sleep");
Method[] declaredMethods = sclass1.getDeclaredMethods();
sleep.setAccessible(true);
sleep.invoke(s);
控制台输出:
好了,具体获取方法都说完了,那么,这玩意有啥用,比我们平时做的还要麻烦!
下面我们做个案例,就是写一个反射,让他可以在不改变代码的情况下,创建任意类的对象,并且调用任意方法!这样就理解了
反射案例(解释为什么反射是框架的灵魂)
反射案例解释
| 反射案例就是想办法实现一个功能:可以在不修改代码的情况下,创建任意类的对象,并且调用该类任意方法。这也是框架的核心思想,所以说反射是框架的灵魂。
初步思考
| 案例要求我们不修改代码,也就是写完的代码要具有通用性,第一次看到这里可能完全没有思路,不过没有关系,我们这样想:想要创建某个类的某个方法,我们得知道具体是那个类的那个方法,不然干瞪眼吗?假设我们知道了要创建猫咪类的睡觉方法(猫咪类定义如下),我们可以尝试用反射完成!多说不易,开工!!!
第一步 创建配置文件
| 创建配置文件,听起来就迷迷糊糊,哎呦,其实就是讲清楚要创建哪个类的对象,调用它的什么方法!具体做法如下
|
在src下新建一个文件(file),名字随意(建议和我这个一样)
在里面写上全类名和方法名
| 这样就创建好了
第二步 加载配置文件
新建一个类,反射类,分别完成以下3步工作,图中每一步的代码用换行隔开了
第三步反射
用上面说的反射获取到对象和方法!并且调用!
第四步运行
换其他类的方法
现在想用狗子类中的吃饭方法,只需要在配置文件中修改一下就好了,并没有修改代码!
这样就实现了反射的案例:创建任意类的对象,调用它的方法。框架中就是用了大量反射来实现自动创建,我们只是需要配置文件就可以。希望可以帮助到大家