20—JAVA(进阶)—反射(Reflection)

01 反射机制

什么是反射?

学过Java的人估计都知道反射,反射可以说是Java中一种非常强大的技术,它可以做的事情太多太多。

有句话说的很好:反射是框架的灵魂

没有反射就没有那么多优秀的框架,理解反射对以后学习框架底层源码会很有帮,很多优秀的开源框架都是通过反射完成的。

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

上面就是对Java的反射机制的描述,读完感觉怎么样,是不是有点懵,那你接着向下看!!!

举例

何为运行状态中动态获取的信息以及动态调用对象的方法,我们看两段代码。

  • 一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

    //先创建一个猫的对象
    Cat cat=new Cat();
    //使用这个对象去调用方法,抓老鼠
    cat.catchMouse();
    
  • 而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了,这时候,我们使用 JDK 提供的反射 API 进行反射调用

    //类的全路径
    String str="com.aismall.reflection.Cat"
    // 加载类,返回Class类类型的对象cls
    Class clz = Class.forName(str);
    // 通过Class类中的newInstance方法,获取我们传入com.aismall.reflection.Cat类的对象
    Object object = cls.newInstance();
    //通过Class类中的getMethod方法,获取com.aismall.reflection.Cat类的catchMouse方法对应的对象
    Method method = clz.getMethod("catchMouse");
    // 通过方法对应的对象来调用该方法类对应的对象
    // 传统是:对象.方法() 
    // 反射是:方法.invoke(对象)
    method.invoke(object);
    

上面两段代码的执行结果是完全一样的,但是其思路完全不一样:

  • 第一段代码在未运行时就已经确定了要运行的类(Cat)。
  • 第二段代码则是在运行时通过字符串值才得知要运行的类(com.aismall.reflection.Cat),然后调用该类的方法,这就是所谓的在运行时动态获取的信息以及动态调用对象的方法

有的人该问了,为什么要这么麻烦,直接new不是更好吗,高搞得花里胡哨的是害怕我们学会吗????

下面我们就来谈谈反射机制的好处!!!!

反射机制的优点

前面提到反射是框架的灵魂,学过框架的小伙伴应该知道框架的开闭原则,就是对扩展开放,对修改关闭,这就体现出反射的好处了。

刚才我们那个Cat类中只有一个方法catchMouse,现在我们想给这个Cat增加一个功能sayMiao

如果我之前代码中通过new的方式创建了Cat的实例,然后调用了这个catchMouse方法,如下:

//先创建一个猫的对象
Cat cat=new Cat();
//使用这个对象去调用方法,抓老鼠
cat.catchMouse();

因为添加了新功能,我们不想使用哪个功能了,我们现在只想让它sayMiao,不想让它catchMouse

可能是这只猫进城了,被当成宠物喵了,每天只要喵喵叫的卖萌就可以了!!!

这时候是不是要修改源码,如下:

//先创建一个猫的对象
Cat cat=new Cat();
//使用这个对象去调用方法,抓老鼠
//cat.catchMouse();
cat.sayMiao();

这样做是违反开闭原则的,拓展可以,修改源码就不可以

如果引入反射机制,我们就可以通过修改配置文件,不动源码,来实现功能的修改,如下:

  • 配置文件内容如下:

    classPath=com.aismall.reflection.Cat
    //methodName=catchMouse
    //我们把methodName的值修改一下,不动下面的源码,就可以实现功能的修改
    methodName=sayMiao
    
    // 读取配置文件
    Properties properties= new Properties ();
    properties.load(new FileInputStream("application.properties"))
    //获取类的全路径
    String classPath=properties.get("classPath").toString();
    //获取方法名
    String methodName=properties.get("methodName").toString();
    
    Class clz = Class.forName(classPath);
    
    Object object = cls.newInstance();
    
    Method method = cls.getMethod(methodName);
    
    method.invoke(object);
    

注意:修改配置文件,不是修改源码重申一下,开闭原则是,对扩展开放(也就是增加功能是可以修改源码的),对修改关闭(其他情况下的修改是不允许的)。

反射机制的缺点

使用反射基本是解释执行,对执行速度有影响

下面让我们一点一点的来深入的学习反射机制吧!!!!!

02 反射机制的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时得到任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理

03 反射相关的类

在这里插入图片描述

  • java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象。
  • java.lang.reflect.Constructor:代表类的构造方法,Constructor对象代表某个类的构造方法。
  • java.lang.reflect.Method:代表类的方法,Method对象代表某个类的方法。
  • java.lang.reflect.Field:代表类的成员变量,Field对象代表某个类的成员变量。

说一下几个注意的点:

  • Method类的对象一般是无法获取类的私有属性
  • 通过Constructor对象的方法不仅可以获取空参构造函数,也可以获取带参数的构造函数。

04 Class类

概述

  • Class也是类,因此也继承Object类,如下图:
    在这里插入图片描述

  • Class类的对象不是new出来的,而是系统创建的。

  • 对于某个类对应的Class类对象,在内存中只有一份,因此类只加载一次(想要详细了解的可以去查:双亲委派机制

    //我们通过反射的方式对Cat类加载两次
    Class cls1=Class.forName("com.aismall.reflection.Cat");
    Class cls2=Class.forName("com.aismall.reflection.Cat");
    //通过打印的hashCode值可以看出,对应的是同一个值,也就是该类只加载了一次
    System.out.println(cls1.hashCode());	//617901222
    System.out.println(cls2.hashCode()); //617901222
    
  • 每个类的实例都会记得自己是由那个Class实例所生成

  • 通过Class类可以完整得到一个类的完整结构,通过一系列API
    在这里插入图片描述

  • Class对象是存放在堆区的

  • 类的字节码二进制数据是存放在方法区的,有的地方也把方法区称为元数据去,用来存放类的元数据(包括方法代码,类名,方法名,访问权限等)

代码演示

在这里插入图片描述
Cat.class

public class Cat {
    //属性
    public String name;
    public int age;

    // 无参构造
    public Cat() {
    }

    public void  catchMouse(){
        System.out.println("猫爪老鼠");
    }
}

testCat.class

public class testCat {
    public static void main(String[] args) {
        //1、获取Class类对象对应的Class(Cat),注意:不是对应的对象,使对应的Class
        Class cls=Class.forName("com.aismall.reflection.Cat");
        //返回值为是哪个类的Class对象,此处为com.aismall.reflection.Cat类的Class对象
        System.out.println(cls);    // class com.aismall.reflection.Cat

        //2、我们也可以通过下面的方法获取生成该Class对象的类
        System.out.println(cls.getClass()); //class java.lang.Class

        //3、获取包名
        System.out.println(cls.getPackage());   //package com.aismall.reflection

        //4、获取全类名
        System.out.println(cls.getName());  //com.aismall.reflection.Cat

        //5、通过Class对象对应的Class(Cat),创建该Class对应的实例(cat)
        Cat cat=(Cat)cls.newInstance();

        //6、通过反射获取对象的属性
        Field age=cls.getField("age");
        System.out.println(age.get(cat));   //0  int类型默认赋值为0

        //7、通过反射给属性赋值
        age.set(cat,666);
        System.out.println(age.get(cat));   //666

        //8、获取所有的字段
        Field[] fields=cls.getFields();
        for (Field field:fields) {
            System.out.println(field.getName());    // name  age
        }

        //9、获取无参构造器
        Constructor constructor=cls.getConstructor();
        Cat cat1=(Cat) constructor.newInstance();
        cat1.catchMouse();  //猫爪老鼠

        //10、Class类中的方法有很多,可以自己尝试着调用一下看看结果
    }
}

获取Class类对象的六种方式

方式一:

  • 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
  • 实例:Class cls1=Class.forName(classPath);
  • 应用场景:多用于配置文件读取类全路径加载类。

方式二:

  • 前提:若已知具体的类,通过类的Class获取,该方式最为安全可靠,程序性能最高
  • 实例: Class cls2=Cat.class;
  • 应用场景:多用于参数传递,比如通过反射得到对应构造器对象。

方式三:

  • 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象。
  • 实例:Class clazz=对象.getClass();
  • 应用场景:通过创建好的对象获取Class对象

方式四:通过类加载器来获取类的对象

方式五:基本数据类型按照如下方式获得Class对象

方式六:基本数据类型对应的包装类,可以通过包装类.TYPE来获取对应的Class对象

public class testCat {
    public static void main(String[] args) throws ClassNotFoundException {
        // 一般都是通过配置文件读取,我们此处为了方便,直接写死
        String classPath="com.aismall.reflection.Cat";
        // 方式一
        Class cls1=Class.forName(classPath);
        System.out.println(cls1);    // class com.aismall.reflection.Cat

        //方式二
        Class cls2=Cat.class;
        System.out.println(cls2); //class com.aismall.reflection.Cat

        // 方式三
        Cat cat=new Cat();
        Class cls3=cat.getClass();
        System.out.println(cls3);   //class com.aismall.reflection.Cat

        //方式四:通过类加载器来获取类的对象
        // 先拿到Cat的类加载器
        ClassLoader classLoader=cat.getClass().getClassLoader();
        // 通过类加载器得到对应的对象
        Class cls4=classLoader.loadClass(classPath);
        System.out.println(cls4);     //class com.aismall.reflection.Cat

        System.out.println(cls1.hashCode());    //617901222
        System.out.println(cls2.hashCode());    //617901222
        System.out.println(cls3.hashCode());    //617901222
        System.out.println(cls4.hashCode());    //617901222

        //方式五:基本数据类型按照如下方式获得Class对象
        Class<Integer> cls5=int.class;
        System.out.println(cls5);

        //方式六:基本数据类型对应的包装类,可以通过包装类.TYPE来获取对应的Class对象
        Class cls6=Integer.TYPE;
        System.out.println(cls6);

        System.out.println(cls5.hashCode());    //1159190947
        System.out.println(cls6.hashCode());    //1159190947
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

彤彤的小跟班

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

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

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

打赏作者

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

抵扣说明:

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

余额充值