Java 反射机制

作者: 未见哥哥 | 来源:发表于2019-04-03 17:37 被阅读260次
    Java 反射机制

    反射

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

    反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法或者属性。

    反射机制主要提供以下几种功能

    • 在运行时构造任意类的对象。
    • 在运行时获取一个类的成员变量和方法。
    • 在运行时调用某个类对象的方法和成员变量。

    在 Java 这个面向对象的语言中,万事万物皆为对象,使用什么来用于表示每一个 Java 类呢?在 Java 中使用 Class 这个类来表示所有的类。

    Class 类

    Class 类用于描述一个类的所有信息,例如构造方法,成员变量,成员方法等,类中每一个元素都有一个对应的类来表示。

    • 成员变量使用 Field 表示
    • 成员方法使用 Method 表示
    • 构造函数使用 Constructor 表示
    • ...

    Class 类常用的方法:

    Class 类

    反射的入口的第一步是首先获取类的 Class 对象

    获取一个类的 Class 对象有三种方式

    • 通过类名.class 获取

    • 通过对象.getClass() 获取

    • 通过 Class.forName() 获取

    Class<Person> personClass = Person.class;
    Class<? extends Person> personClass2 = new Person().getClass();
    //forName 还不知道具体的类
    Class<?> personClass3 = Class.forName("com.example.reflect.Person");
    

    Constructor

    Constructor 代表某个类的构造方法。

    获取构造类的 Construtor 对象

    • 获取类中所有的构造方法
    Constructor<String>[] constructors = (Constructor<String>[]) Class.forName("java.lang.String").getConstructors();
    System.out.println("---------------获取所有的构造器start---------------");
    for (Constructor<?> constructor : constructors) {
        System.out.println(constructor);
    }
    System.out.println("---------------获取所有的构造器end---------------");
    
    • 获取类的某个构造方法
    Constructor<byte[]> constructor = (Constructor<byte[]>) Class.forName("java.lang.String").getConstructor(byte[].class);
    

    创建对象

    • 正常创建实例对象
    String str = new String(new StringBuffer("Hello World"));
    
    • 反射方式创建对象的两种方式

    方式一:

    //拿到指定构造器Constructor对象
    Constructor<?> constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
    //通过 newInstance 创建对象
    String str = (String) constructor.newInstance( new StringBuffer("Hello World"));
    

    方式二:

    该方法Class.forName内部会得到无参构造方法 Constroctor 实例,然后调用 Constructor.newInstance(),它内部会使用缓存机制来保存默认构造方法的示例对象。

    Class.forName("java.lang.String").newInstance();
    

    通过源码分析 Class.forName("java.lang.String").newInstance()内部是如何实现缓存的?

    在 ① 处判断缓存的 cachedConstructor是否为空,它表示的是当前对象的一个无参构造,第一次使用时肯定是为空,那么通过 ② getConstructor0 得到一个 Constructor 无参实例。如果缓存的 cachedConstructor不为空,那么直接通过③直接创建对象 Constructor.newInstance()。

    如果一个类没有无参构造,而直接去调用 Class.forName(...).newInstance() 则会报错。

    //Class.java
    public T newInstance()
        throws InstantiationException, IllegalAccessException
    {
        //①
        if (cachedConstructor == null) {
            //代码省略...
            try {
                Class<?>[] empty = {};
                //② 
                final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
                cachedConstructor = c;
            } catch (NoSuchMethodException e) {
                throw (InstantiationException)
                    new InstantiationException(getName()).initCause(e);
            }
        }
        Constructor<T> tmpConstructor = cachedConstructor;
        //代码省略...
        // Run constructor
        try {
            //②创建对象
            return tmpConstructor.newInstance((Object[])null);
        } catch (InvocationTargetException e) {
            Unsafe.getUnsafe().throwException(e.getTargetException());
            // Not reached
            return null;
        }
    }
    

    下面是具体 Class.newInstance()内部调用逻辑

    Class.newInstance()内部调用逻辑

    Method

    Method 代表某个类的一个方法。

    获取 Method 的方式有两种

    获取当前类和父类中所有的 public 方法。

    public Method getMethod(String name, Class<?>... parameterTypes)
    

    获取本类中所有方法,不包括父类方法。

    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    

    注意:通过 invoke 方法调用私有方法时,需要设置 method.setAccessible(true);

    实践:通过反射调用 DoSomething 的 main 方法。

    //DoSomething.java
    package com.example.reflect;
    public class DoSomething {
        public static void main(String[] args) {
            System.out.println(DoSomething.class.getSimpleName());
            for (String arg : args) {
                System.out.println(arg);
            }
        }
    
        @Override
        public int hashCode() {
            return super.hashCode();
        }
    }
    
    • 获取表示 main 方法的 Method 对象
    Class<?> clazz = Class.forName("com.example.reflect.DoSomething");
    //public static void main(String[] args)
    Method mainMethod = clazz.getMethod("main", String[].class);
    
    • 反射调用 main 方法
    //调用 main 方法。
    mainMethod.invoke(null, new Object[]{new String[]{"Hello"}});
    

    这里需要注意一点 JDK1.4 和 JDk1.5 关于 method#invoke 方法的区别:

    JDK1.5 public Object invoke(Object obj,Object...args)
    JDK1.4 public Object invoke(Object obj,Object[] args)
    

    如果是以一个字符串数组传入给 invoke 方法,那么 javac 会按照哪种语法给处理呢?
    因为 JDK1.5 需要兼容 JDk1.4 因此会按照 JDK1.4 的语法来执行。
    表示传入的 method 的参数 new Object[]{},这个数组存放的元素才是真正我们要传入的参数,那么这个数组需要存放的是 String[]{"","",""}数组
    所以最终表示为:method.invoke(null,new Object[]{new String[]{"Hello"}})

    Field

    Field 表示一个类的某一个成员变量。

    public class Person {
    
        private int age;
        private String name;
    
    
        public Person() {
        }
    
        public Person(int age, String name) {
            this.age = age;
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    

    实践:反射获取一个对象的成员变量和给该成员变量赋值。

    //Field 类:代表某个类的一个成员变量
    Class<Person> clazz = (Class<Person>) Class.forName("com.example.reflect.Person");
    Field ageField = clazz.getDeclaredField("age");
    //私有属性需要设置ageField.setAccessible(true)
    ageField.setAccessible(true);
    Person person = clazz.newInstance();
    person.setAge(11);
    //获取 age 属性的值
    int age = (int) ageField.get(person);
    System.out.println(age);
    //给 age 属性赋值
    ageField.set(person,12);
    int ageValue = (int) ageField.get(person);
    System.out.println(ageValue);
    

    记录于2019年4月3日

    相关文章

      网友评论

        本文标题:Java 反射机制

        本文链接:https://www.haomeiwen.com/subject/pymzbqtx.html