前言

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

应用

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




java反射判断属性类型是否是基本类型 java反射判断字段类型_java反射判断属性类型是否是基本类型


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


java反射判断属性类型是否是基本类型 java反射判断字段类型_User_02


最后一个用途是利用反射破坏单例

定义

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

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

类对象

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


java反射判断属性类型是否是基本类型 java反射判断字段类型_System_03


获取类对象有四种方式:


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;


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


java反射判断属性类型是否是基本类型 java反射判断字段类型_反射 字段_04


先看 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());
        }


执行一下


java反射判断属性类型是否是基本类型 java反射判断字段类型_System_05


大家结合上面的类的代码对比一下,其实区别我已经写在注释上了,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);


方法操作

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


java反射判断属性类型是否是基本类型 java反射判断字段类型_java反射判断属性类型是否是基本类型_06


先看下 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 是获取本类中所有的方法,包括私有的。


java反射判断属性类型是否是基本类型 java反射判断字段类型_反射 字段_07


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


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


执行一下


java反射判断属性类型是否是基本类型 java反射判断字段类型_字段_08


构造器操作

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


// 获取所有的公有的构造器
        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 获取的是类中所有的构造器,包括私有的。


java反射判断属性类型是否是基本类型 java反射判断字段类型_System_09


当然构造器的应用场景就是创建对象,接下来顺便说下创建对象的几种方式。第一种就是最常见的 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));


运行一下


java反射判断属性类型是否是基本类型 java反射判断字段类型_反射 字段_10


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

总结

反射到这里就全部结束了,本篇文章介绍了一些常用的方法以及应用场景,希望能够给大家带来帮助,反射是高手玩的东西


作者: Jack_xu