Java反射机制

概述

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类结构信息。我们可以通过这个对象看到类的结构。
  • 这个Class对象就像一面镜子,通过这个镜子看到类的结构,所以我们形象的称之为:反射

实例化对象的正常方式:

​ 引入需要的“包类”名称——>通过new实例化——>取得实例化对象

实例化对象的反射方式:

​ 实例化对象——>getClass()方法——>得到完整的“包类”名称

补充:动态语言&静态语言

动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang

静态语言

与动态语言对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活

反射提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

反射相关的主要API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor代表类的构造器

疑问

通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?

建议直接new的方式

什么时候会使用反射的方式

反射具有动态性,所以当我们不确定要实例化哪个类时,可以使用反射

反射机制与面向对象中的封装行是不是矛盾的?如何看待两个技术?

肯定不矛盾啦~

封装性是将属性和方法封装起来,只对外暴露公有的属性和方法,这里的意思是,建议你使用公有的,私有的是供内部自己使用的,封装者认为你不需要使用,公有的已经足够满足需求(比如公有的方法中调用了私有方法并进行了增强,你就没必要去直接调用私有方法)。

反射是让我们能够去操作一个类的私有和公有方法和属性,让我们有这个能力,在需要的时候可以借助反射来实现对类私有方法或私有属性的操作

入门

关于java.lang.Class的理解

具体类的加载过程见JVM笔记
gitee:
https://gitee.com/importguitar/jvm.git
github:
https://github.com/importGuitar/JVM.git

  • 类的加载过程

    • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。

    • 接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。此过程就成为类的加载。加载到内存中的类,我们就成为运行时类,此运行时类,就作为Class的一个实例。

    • 即:每个运行时类都是Class类型的对象,也就是说,类也是对象,是什么类型的对象呢?是Class类型的对象。

      • 如:

        Class clazz = Person.class;
        
  • 换句话说:Class的实例就对应着一个运行时类

  • 加载到内存中的运行时类,会缓存一定的时间,在此时间内,我们可以以不同的方式去获取该运行时类,获取到的都是同一个运行时类。

获取Class实例的四种方式

/**
     * 获取Class实例的四种方式
     */
    @Test
    public void testGetClass() throws ClassNotFoundException {

//        方式一:调用运行时类的属性.class
        Class<Person> personClass = Person.class;

//        方式二:通过运行时类的对象,调用getClass()方法
        Person person = new Person();
        Class<? extends Person> personClass1 = person.getClass();

//        方式三:调用Class的静态方法forName(String classPath)
//        该方式比较常用,因为:
//        方式一已经写死了具体类,编译期就确定了要加载哪个类;
//        方式二已经创建了具体对象,没必要再用反射
//        方式三可以将要加载的类以字符串参数的形式传入,来动态加载
//        方式四要调用ClassLoader,不常用
        Class<?> personClass2 = Class.forName("cn.shangxiaoying.reflect.Person");

//        方式四:使用类的加载器
        ClassLoader classLoader = Person.class.getClassLoader();
        Class<?> personClass3 = classLoader.loadClass("cn.shangxiaoying.reflect.Person");

//        获取的是同一个运行时类的Class对象,true
        System.out.println(personClass == personClass1 && personClass == personClass2 && personClass == personClass3);
    }

Class实例可以是哪些结构?

  • class:类
    • 外部类
    • 成员
      • 成员内部类
      • 静态内部类
    • 局部内部类
    • 匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解@interface
  • private type:基本数据类型
  • void:方法的返回类型,void也算一种类型

代码演示:

 /**
     * Class实例可以是哪些结构
     */
    @Test
    public void testClassType() {
        Class<Object> objectClass = Object.class;
        Class<Comparable> comparableClass = Comparable.class;
        Class<String[]> stringArrClass = String[].class;
        Class<int[][]> intArrClass = int[][].class;
        Class<ElementType> elementTypeClass = ElementType.class;
        Class<Override> overrideClass = Override.class;
        Class<Integer> integerClass = int.class;
        Class<Void> voidClass = void.class;
        Class<Class> classClass = Class.class;

        int[] a = new int[10];
        int[] b = new int[100];
        Class<? extends int[]> aClass = a.getClass();
        Class<? extends int[]> bClass = b.getClass();

//        只要数组的元素类型与维度(一维、二维...)一样,就是同一个Class
//        true
        System.out.println(aClass == bClass);
    }

基操

创建实例

@Test
    public void testnewInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class<?> personClass = Class.forName("cn.shangxiaoying.reflect.Person");
        /**
         * 要保证newInstance()方法能够正常运行,需要满足:
         *  运行时类(这里的Person类)必须提供空参构造器,因为它会调用空参构造器来创建实例
         *  空参构造器的访问权限要足够(即newInstance()方法能够访问的到),通常设置为public
         */
        Object obj = personClass.newInstance();
        System.out.println(obj);
    }

JavaBean中为何必须提供Public的空参构造器?

  • 便于通过反射,创建运行时类的对象。(一些框架的底层都是通过反射来创建对象,用的就是newInstance()方法。)
  • 便于子类继承此运行时类时,在子类的构造器中默认调用super()时,保证父类有次构造器。

获取运行时类的属性信息

/**
     * 通过反射获取运行时类的属性信息
     */
    @Test
    public void testGetFields() {
        Class<Person> personClass = Person.class;

        /**
         * 获取当前运行时类及其所有父类中声明为public的属性
         */
        Field[] fields = personClass.getFields();

        /**
         * 获取当前运行时类中声明的所有(不论权限)属性(不包含其父类中的属性)
         */
        Field[] declaredFields = personClass.getDeclaredFields();

        for (Field field : declaredFields) {

            /**
             * 获取变量的权限修饰符
             */
            int modifier = field.getModifiers();
            System.out.println(Modifier.toString(modifier));

            /**
             * 获取变量的数据类型
             */
            Class<?> type = field.getType();
            System.out.println(type.getName());

            /**
             * 获取变量名
             */
            String name = field.getName();
            System.out.println(name);
        }
    }

获取运行时类的方法信息

/**
     * 获取运行时类的方法信息
     */
    @Test
    public void testGetMethods() {
        Class<Person> personClass = Person.class;

        /**
         * 获取当前运行时类及其所有父类声明为public权限的方法
         */
        Method[] methods = personClass.getMethods();

        /**
         * 获取当前运行时类声明的所有(不论权限)方法(不包含父类)
         */
        Method[] declaredMethods = personClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            /**
             * 获取方法声明的注解
             */
            Annotation[] annotations = method.getAnnotations();

            /**
             * 获取方法的权限修饰符
             */
            int modifiers = method.getModifiers();
            System.out.println(Modifier.toString(modifiers));

            /**
             * 获取方法的返回值类型
             */
            Class<?> returnType = method.getReturnType();

            /**
             * 获取方法名
             */
            String name = method.getName();

            /**
             * 获取方法的形参列表
             */
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println(parameterType.getName());
            }

            /**
             * 获取方法抛出的异常
             */
            Class<?>[] exceptionTypes = method.getExceptionTypes();
            for (Class<?> exceptionType : exceptionTypes) {
                System.out.println(exceptionType.getName());
            }
        }
    }

获取构造器信息

 /**
     * 获取构造器信息
     */
    @Test
    public void testGetConstructors() {
        Class<Person> personClass = Person.class;

        /**
         * 获取当前运行时类声明为public的构造器
         */
        Constructor<?>[] constructors = personClass.getConstructors();

        /**
         * 获取当前运行时类的所有构造器
         */
        Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();

        /**
         * 
         * 获取构造器的权限修饰符、参数等方式与获取方法的修饰符等类似
         */
    }

获取父类信息

/**
     * 获取当前运行时类的父类信息
     */
    @Test
    public void getSuperClass() {
        Class<Person> personClass = Person.class;

        /**
         * 获取当前运行时类的父类,不带泛型
         */
        Class<? super Person> superclass = personClass.getSuperclass();

        /**
         * 获取当前运行时类的父类,带泛型
         */
        Type genericSuperclass = personClass.getGenericSuperclass();

        /**
         * 获取父类的泛型
         */
        ParameterizedType type = (ParameterizedType) genericSuperclass;
        Type[] actualTypeArguments = type.getActualTypeArguments();
        for (Type actualTypeArgument : actualTypeArguments) {
            System.out.println(actualTypeArgument.getTypeName());
        }
    }

获取接口信息

/**
     * 获取当前运行时类实现的接口信息
     */
    @Test
    public void testGetInterfaces() {
        Class<Person> personClass = Person.class;
        /**
         * 获取当前运行时类实现的接口
         */
        Class<?>[] interfaces = personClass.getInterfaces();

        /**
         * 获取当前运行时类父类实现的接口
         */
        Class<?>[] interfaces1 = personClass.getSuperclass().getInterfaces();
    }

获取所在包

/**
     * 获取当前运行时类所在的包
     */
    @Test
    public void testGetPackge() {
        Class<Person> personClass = Person.class;

        /**
         * 获取当前运行时类所在的包
         */
        Package pac = personClass.getPackage();
    }

获取注解信息

/**
     * 获取当前运行时类声明的注解
     */
    @Test
    public void testGetAnnotation() {
        Class<Person> personClass = Person.class;

        /**
         * 获取当前运行时类声明的注解
         */
        Annotation[] annotations = personClass.getAnnotations();
    }

操作指定属性

/**
     * 操作运行时类中的指定属性
     */
    @Test
    public void testOperateField() throws Exception {
        Class<Person> personClass = Person.class;
//        创建实例(要设置的属性不是静态属性时需要创建实例)
        Person person = personClass.newInstance();
//        获取属性
        /**
         * getField()方法只能获取public权限的属性
         */
//        Field age = personClass.getField("age");

        /**
         * getDeclaredField()方法可以获取任意访问权限的属性,一般都使用这个方法
         */
        Field age = personClass.getDeclaredField("age");
        /**
         * 对于非public权限的字段不能直接修改,需要setAccessible(true)之后才能修改他的值
         */
        age.setAccessible(true);

       /**
         * 设置属性
         * set(要设置哪个对象的属性, 要设置的值)
         * 对于静态属性:
         *  set(null或者Class对象, 要设置的值)
         */
        age.set(person, 22);
        /**
         * 获取属性
         * get(要获取哪个对象的值)
         */
        Integer personAge = (Integer) age.get(person);
        System.out.println(personAge);
    }

操作指定方法

/**
     * 操作指定方法
     */
    @Test
    public void testOperateMethod() throws Exception {
        Class<Person> personClass = Person.class;

        /**
         * 创建实例(非静态方法需要创建实例)
         */
        Person person = personClass.newInstance();
        /**
         * 获取指定方法
         * getDeclaredMethod(方法名, 可变形参类型列表)
         */
        Method show = personClass.getDeclaredMethod("show", String.class);

        /**
         * 对于非publ权限的方法,需要设置可访问
         */
        show.setAccessible(true);

        /**
         * 调用方法
         * invoke(方法的调用者, 可变实参列表)
         * invoke()方法的返回值即为调用的方法的返回值,如果调用的类没有返回值,则invoke()方法返回null
         * 相当于:
         * String showContent = person.show("吉他弹唱");
         */
        Object showContent = show.invoke(person, "吉他弹唱");
        System.out.println(showContent);

        /**
         * 调用静态方法
         */
        Method sayHi = personClass.getDeclaredMethod("sayHi");
        sayHi.setAccessible(true);
//        调用者可以传一个Class对象,也可以传null
        sayHi.invoke(personClass);
    }

操作指定构造器

不常用,一般使用newInstance()调用运行时类的无参构造器

/**
     * 操作指定构造器
     */
    @Test
    public void testOperateConstructor() throws Exception {
        Class<Person> personClass = Person.class;
        /**
         * 获取指定构造器
         * getDeclaredConstructor(可变参数类型列表)
         */
        Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
        /**
         * 保证可访问
         */
        declaredConstructor.setAccessible(true);
        /**
         * 调用指定构造器
         * newInstance(可变实参列表)
         */
        Person person = declaredConstructor.newInstance("豆包");
        System.out.println(person);
    }

Q.E.D.


Read The Fucking Source Code!