前言:
今天简单学习了下反射,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+"碗饭");
}
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 {
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 sclass1 = Class.forName("cn.itcast.person.Student");
Class sclass2 = Student.class;
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 {
Student s = new Student("郭德纲", 45, "男", 170);
Class sclass3 = s.getClass();
Field name = sclass1.getField("name");
Field age = sclass1.getField("age");
Field sex = sclass1.getField("sex");
Field height = sclass1.getField("height");
Field[] fields = sclass1.getFields();
Field name1 = sclass1.getDeclaredField("name");
Field age1 = sclass1.getDeclaredField("age");
Field sex1 = sclass1.getDeclaredField("sex");
Field height1 = sclass1.getDeclaredField("height");
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种,代码如下
*/
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下建一个pro.properties文件(file),在文件里写如下(第二张图)内容,全路径包名和想调用的方法名
获取配置文件
加载配置文件
获取配置文件的值
反射
以上四部分统一写在代码里
public class ReflectTest {
public static void main(String[] args) throws Exception {
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
Properties p = new Properties();
p.load(is);
String classname = p.getProperty("classname");
String methodname = p.getProperty("methodname");
Class<?> aClass = Class.forName(classname);
Constructor<?> constructor = aClass.getConstructor();
Object o = constructor.newInstance();
Method method = aClass.getMethod(methodname);
method.invoke(o);
}
}
这样,加入我要调用学生类的其他方法,只要在配置文件(pro.properties)修改就可以了,就不用变化代码了。 甚至,我新建一个老师类,想创建老师类对象,并且调用老师类中方法,也只需要修改配置文件中的classname,methodname就可以了。
以上仅代表个人观点。有帮助请您点个赞,如有错误,还请斧正。
|