通俗的讲一下Java中的反射,简单易懂

前言:

今天简单学习了下反射,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步工作,图中每一步的代码用换行隔开了
在这里插入图片描述

第三步反射

用上面说的反射获取到对象和方法!并且调用!
在这里插入图片描述

第四步运行

在这里插入图片描述

换其他类的方法

现在想用狗子类中的吃饭方法,只需要在配置文件中修改一下就好了,并没有修改代码!
在这里插入图片描述

这样就实现了反射的案例:创建任意类的对象,调用它的方法。框架中就是用了大量反射来实现自动创建,我们只是需要配置文件就可以。希望可以帮助到大家

以上仅代表个人观点。有帮助请您点个赞,如有错误,还请斧正。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兰林汉的驴粉丝儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值