java反射 (reflection) 详解

        各位,java反射我们作为一个java语言搬运工经常看见。但是我们知道反射是什么?为什么要用反射?,反射会不会带来性能上的低下?带着这些问题我们来一起探讨一下java反射吧!

1.什么是java反射(reflection)?

java反射其实就是在程序运行状态下去动态的获取 类的 对象、方法及其所有属性去进行操作,所谓动态获取是如何去获取的呢?

这里我们就要知道java虚拟机类加载机制及其过程,如果不了解java类加载机制的我建议您先去看一下java虚拟机相关内容。

来我们一起看  :Student student = new Student();  是如何建立一个对象的和加载过程的?java反射 (reflection) 详解

上图是新建一个p的jvm加载的过程:

1.当我们调用new People()操作时jvn虚拟机会去本地方法区查找是否存在People.class对应的文件对象。

    如果存在 :执行4

        4.执行类的初期化函数操作(我上图简略调了初始化方法的进栈操作,直接在heap里面执行对象初始化)新建People对象。

    如果不存在:执行2---》3---》4

        2.类加载器去本地加载(读取)People.class文件

        3.读取成功后存储到方法区中。(其实这个读取操作我上图已经为初学者看得明白已经减少中间的一些操作,其实是先读取到.class 文件然后解析成jvn能解析的.dex文件,最后才保存.class文件对象到方法区)

        4.当我们调用new People()操作时jvn虚拟机会去本地方法区查找是否存在People.class对应的文件对象。

5把得到的对象地址赋予给p。

6方法出栈,完成一个类的加载过程。

我们再回头理解一下什么是反射,反射其实就是动态获取到.class文件对象,然后通过文件对象 去获取类 的各个方法和及其属性的对象,然后通过获取到的对象去调用方法来进行操作来达到动态改变。(这里来重点了,我们每次操作对象中的方法都必须要去新建一个对象,然后再去找到相应的方法 再去调用,这个过程比我们静态新建调用一个方法多了 查找方法来获取到方法对象 再调用方法这个步骤其实是非常耗性能的,所有我们一般情况下能不用用反射就不用,因为反射会导致程序效率下降。但既然不建议我们用发射,为什么我们发现发射无处不在呢?因为反射也有值得用到的地方,因为它也有优点。这就是我们接下来要解答的为什么要用发射)。


2.为什么要用反射(reflection)?

反射本质是程序运行时动态获取类对象进行操作,既然能动态操作对象当然可以增加程序的类活性,降低代码臃肿。

3.下面我们就以Demo来演示一下如何通过发射来增加程序的灵活性和发射的实际操作。

    3-1 People 类如下:

public class People {
    private String mName;
    private Integer mAge;

    private People() {
    }

    private People(String mName) {
        this.mName = mName;
    }

    public People(Integer mAge) {
        this.mAge = mAge;
    }

    public People(String mName, Integer mAge) {
        this.mName = mName;
        this.mAge = mAge;
    }

    public String getmName() {
        return mName;
    }

    public void setmName(String mName) {
        this.mName = mName;
    }

    public Integer getmAge() {
        return mAge;
    }

    public void setmAge(Integer mAge) {
        this.mAge = mAge;
    }

    @Override
    public String toString() {
        return "People{" +
                "mName='" + mName + '\'' +
                ", mAge=" + mAge +
                '}';
    }
}

上图People类既有公有方法也有私有方法,我们通过反射获取到方法对象,在执行前如果该方法是私有的,则要执行setAccessable(true)解除权限限制,否则报 illegalAccessException.

    3-2 ReflectionExplame 类如下:

public class ReflectionExplame {

    /**
     * 获取所有公有构造函数
     * @param classPath
     */
    public void getConstructor(String classPath) {
        Class peopleClass = null;
        try {
            peopleClass = Class.forName(classPath);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        Constructor[] constructor = peopleClass.getConstructors();
        for (Constructor constructor1 : constructor) {
            System.out.print("所有公有构造方法:---" + constructor1.toString() + "\n");
        }

    }

    /**
     * 获取所有构造方法
     * @param classPath
     */
    public void getDeclareConstructor(String classPath) {
        Class peopleClass = null;
        try {
            peopleClass = Class.forName(classPath);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Constructor[] declaredConstructors = peopleClass.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors) {
            System.out.print("所有构造方法:---" + constructor.toString() + "\n");
        }
    }

    /**
     * 设置属性
     * @param classPath
     * @param age
     */
    public void setAgeAttribute( String classPath,Integer age){
        Class peopleClass = null;
        try {
            peopleClass = Class.forName("cn.eeye.demo.People");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Constructor mConstructorP;
        Field mName;
        Object mPeople;
        try {
            mConstructorP = peopleClass.getDeclaredConstructor(String.class);
            mConstructorP.setAccessible(true);//.暴力反射,解除权限限制 ,否则 IllegalAccessException
            mPeople = mConstructorP.newInstance("小明");
            mName = peopleClass.getDeclaredField("mAge");
            mName.setAccessible(true);
            mName.set(mPeople, age);
            System.out.print("调用类设置属性mAge成功---"+((People) mPeople).toString() + "\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 调用类其他方法
     * @param classPath
     */
    public void setAgeMethod(String classPath, Integer age) {
        try {
            Class peopleClass = Class.forName(classPath);
            Object people = peopleClass.getDeclaredConstructor(Integer.class).newInstance(age);
            Method method = peopleClass.getDeclaredMethod("setmName",String.class);
            method.setAccessible(true);
            method.invoke(people,"小白");
            System.out.print("调用类setmName()成功---"+((People)people).toString()+"\n");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

这里要注意的就是:通过反射获取到方法对象,在执行方法时要注意该方法是否私有,私有要解除私有限制再执行,另外获取某一个方法时需要准确传入该方法的方法名和参数类型。因为方法名和参数确定一个方法。

    3-3        main 方法,程序入口

public class Main {

    public static void main(String[] args) throws NoSuchFieldException {

        ReflectionExplame reflectionExplame = new ReflectionExplame();
        String classPath = "cn.eeye.demo.People";
        reflectionExplame.getConstructor(classPath);
        reflectionExplame.getDeclareConstructor(classPath);
        reflectionExplame.setAgeMethod(classPath,34);
        reflectionExplame.setAgeAttribute(classPath,23);


    }


}

    3-4 执行方法打印如下:

所有公有构造方法:---public cn.eeye.demo.People(java.lang.String,java.lang.Integer)
所有公有构造方法:---public cn.eeye.demo.People(java.lang.Integer)
所有构造方法:---public cn.eeye.demo.People(java.lang.String,java.lang.Integer)
所有构造方法:---public cn.eeye.demo.People(java.lang.Integer)
所有构造方法:---private cn.eeye.demo.People(java.lang.String)
所有构造方法:---private cn.eeye.demo.People()
调用类setmName()成功---People{mName='小白', mAge=34}

调用类设置属性mAge成功---People{mName='小明', mAge=23}

总结:

还有一点我没有说,就是反射可以越过泛型。因为泛型只在编译器编译时起作用,编译后泛型会被擦除,而且反射是运行执行的。

reflection其实使用起来很简单,但是为了加深自我对反射的理解所以把自己所理解出来的东西写出来分享给有需要的伙伴,让我们共同进步。上文如果写得有不对的地方或者有不好理解的地方请大家给我建议,我继续修改。如果大家想更深一步理解我建议有空时可以多看看jvm虚拟机原理,这样理解起来更加得心应手。好啦,时间不早了,洗洗睡