java核心基础之反射

前言

大家好,我是 jack xu,今天跟大家介绍核心基础里面的反射,反射这个东西你说它重要也重要,不重要也不重要。重要是当你看一些框架的源码时候,里面会用到反射的代码,你不会是看不懂的。不重要是因为我们平时的工作中绝大多数都是在写业务代码,真正操作类的场景很少。这个跟英语一样,不会不影响你的生活,但是当你往上层高层走的时候,不会会制约你的发展。

应用

我把我在工作中用的场景给大家举下例子,加强一下大家学习的信心。一个是我在写 openapi 时候,做一个切面的拦截,获取请求参数,代码如下

另一个是我在写单测的时候,公有的方法很好测,我直接写对象.方法名即可,由于我测的是私有的方法,你直接点是点不出来的,这时候怎么办,就是反射登场的时候,代码如下

最后一个用途是利用反射破坏单例,大家可以看下我以前写的文章(单例模式),以及破坏的预防。

定义

什么是反射,动态获取类的内容以及动态调用对象的方法和获取属性的机制,就叫做 JAVA 的反射机制。

  • 对于给定的一个类 ( Class ) 对象,可以获得这个类 ( Class ) 对象的所有属性和方法。
  • 对于给定的一个对象 ( new XXXClassName<? extends Object> ) ,都能够调用它的任意一个属性和方法。

类对象

首先我们看下一个类对象里面包含了很多的东西,我们可以通过反射拿到这些东西。

获取类对象有四种方式:

        Class<User> clazz1 = User.class;
        Class<?> clazz2 = Class.forName("com.jackxu.reflect.User");
        Class<? extends User> clazz3 = new User().getClass();
        //类加载器
        Class<?> clazz4 = User.class.getClassLoader().loadClass("com.jackxu.reflect.User");

获取类对象以后,我们就可以获取图上的一些结构信息

        // 获取类的修饰符
        System.out.println(clazz1.getModifiers());
        System.out.println(clazz1.getPackage());
        System.out.println(clazz1.getName());
        System.out.println(clazz1.getSuperclass());
        System.out.println(clazz1.getClassLoader());
        System.out.println(clazz1.getSimpleName());
        // 获取类似实现的所有的接口
        System.out.println(clazz1.getInterfaces());
        System.out.println(clazz1.getAnnotations());

字段操作

首先交代两个类,一个是 Person 类,有如下一些信息

public class Person {

    public String idCard;

    private String userName;

    public void fun1() {
    }

    private void fun2() {
    }
}

其次是 User 类,User 类继承了 Person 类

public class User extends Person {

    private String name;

    public String sex;

    public static String address;

    public User() {
    }

    private User(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    private void jump() {
        System.out.println("jump ... ");
    }

    public static void say(String msg) {
        System.out.println("say:" + msg);
    }

}

现在开始我们的操作,依然是获取一个类对象

        Class<User> clazz1 = User.class;

我们来看这个类对象下面关于字段的方法有四个,我们依次来看下

先看 getFields 和 getDeclaredFields 方法有什么区别

        //获取当前类以及父类中公有的字段
        Field[] fields = clazz1.getFields();
        for (Field f : fields) {
            System.out.println(f.getModifiers() + " " + f.getName());
        }
        System.out.println("--------------------------------------------");
        //只能获取当前类中所有的字段
        Field[] declaredFields = clazz1.getDeclaredFields();
        for (Field f : declaredFields) {
            System.out.println(f.getModifiers() + " " + f.getName());
        }

执行一下

大家结合上面的类的代码对比一下,其实区别我已经写在注释上了,getFields 方法获得的是当前类以及父类中所有公有的字段,而 getDeclaredFields 方法获取的只是本类中的所有字段,包括私有的。知道这两个方法区别以后,我们来看下另一个方法 getDeclaredField,这是获取当前类的某个具体字段,如下我们获取的是 name 字段,由于 name 字段是私有的,所以需要把访问权限设为 true,最后把 user 对象的 name 字段的值更改为jack xu。

        Field nameFiled = clazz1.getDeclaredField("name");
        nameFiled.setAccessible(true);
        nameFiled.set(user, "jack xu");
        System.out.println(user.getName());

如果是静态字段,没有具体的对象,那么在 set 方法的第一个参数设为 null 即可。

        Field addressFiled = clazz1.getDeclaredField("address");
        addressFiled.set(null, "shanghai");
        System.out.println(User.address);

方法操作

方法的操作和字段的操作类似,我们同样看下它下面的几个方法

先看下 getMethods 和 getDeclaredMethods 方法

        //获取当前类以及父类中所有公有的方法
        Method[] methods = clazz1.getMethods();
        for (Method m : methods) {
            System.out.println(m.getModifiers() + " " + m.getName());
        }
        System.out.println("--------------------------------------------");
        // 获取本类中的所有的方法,包括私有的
        Method[] declaredMethods = clazz1.getDeclaredMethods();
        for (Method m : declaredMethods) {
            System.out.println(m.getModifiers() + " " + m.getName());
        }

执行一下,我们发现 getMethods 获取的方法有很多, 这是因为 getMethods 方法是获取获取当前类以及父类中所有公有的方法,而 getDeclaredMethods 是获取本类中所有的方法,包括私有的。

方法的调用也是一样,getDeclaredMethod 方法后面第一个参数是方法名,后面传该方法的参数类型(如果有),如果私有方法的话也需要放开下权限,使用的时候,如果是静态方法 set 第一个参数传 null,非静态方法则传对应的实例对象以及参数。

        Method jumpMethod = clazz1.getDeclaredMethod("jump");
        // 放开私有方法的调用
        jumpMethod.setAccessible(true);
        jumpMethod.invoke(user);
        // 静态方法调用
        Method sayMethod = clazz1.getDeclaredMethod("say", String.class);
        sayMethod.invoke(null, "大家好");

执行一下

构造器操作

构造器的操作方法和上面一样,也是大同小异的

        // 获取所有的公有的构造器
        Constructor<?>[] constructors = clazz1.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c.getModifiers() + " " + c.getName());
        }
        System.out.println("--------------------------------------------");
        // 获取所有的构造器
        Constructor<?>[] declaredConstructors = clazz1.getDeclaredConstructors();
        for (Constructor c : declaredConstructors) {
            System.out.println(c.getModifiers() + " " + c.getName());
        }

getConstructors 获取的是所有公有的构造器,getDeclaredConstructors 获取的是类中所有的构造器,包括私有的。

当然构造器的应用场景就是创建对象,接下来顺便说下创建对象的几种方式。第一种就是最常见的 new 的方式,第二种是通过类对象的 newInstance 方法,第三种是通过构造器的 newInstance 方法,第四种是类实现实现 Cloneable 接口,调用 clone 方法,第五种是序列化与反序列化。

        //1、new一个
        User user1 = new User();
        //2、类对象的newInstance方法
        User user2 = clazz1.newInstance();
        //3、构造器的newInstance方法
        Constructor<User> declaredConstructor = clazz1.getDeclaredConstructor(String.class, String.class);
        declaredConstructor.setAccessible(true);
        User user3 = declaredConstructor.newInstance("jack xu", "男");
        //4、clone
        //5、Serializable

反射的优缺点

最后讲下反射的优缺点。。

优点

  • 增加程序的灵活性,避免将固有的逻辑程序写死到代码里
  • 代码简洁,可读性强,可提高代码的复用率

缺点

  • 相较直接调用在量大的情景下反射性能下降
  • 内部暴露和安全隐患
    我们来做下实验,通过new的方式和反射的方式分别创建一千万个对象,看看谁的用时少。
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            User user = new User();
        }
        long end = System.currentTimeMillis();
        System.out.println("用new的方式创建总耗时:" + (end - start));

        Class<User> clazz = User.class;
        for (int i = 0; i < 10000000; i++) {
            clazz.newInstance();
        }
        long end2 = System.currentTimeMillis();
        System.out.println("用反射的方式创建总耗时:" + (end2 - end));

运行一下

结果出来了,用反射的方式慢了大概8倍的样子,所以反射的性能低不是随便说说的,是有数据为证的,那具体慢在哪里呢,主要是因为调用了本地的 native 方法以及每次 newInstance 都会做安全检查,比较耗时。

总结

反射到这里就全部结束了,本篇文章介绍了一些常用的方法以及应用场景,希望能够给大家带来帮助,反射是高手玩的东西,但谁又何尝不想成为一个高手呢,在成为大神的路上努力吧,如果你觉得写的不错,麻烦点一下赞哦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值