美文网首页技术干货
Java反射基本知识

Java反射基本知识

作者: HeyLehr | 来源:发表于2020-02-17 16:17 被阅读0次

    Class类

    Class类是什么

    Java核心技术卷的官方话来说:
    在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类,虚拟机利用运行时类型信息选择相应的方法执行。我们子在编程时可以通过专门的Java类访问这些信息,保存这些信息的类就被称为Class。

    简单地说,就是:
    在Java中,有一个类,专门来描述类本身,这就是Class类,它能描述一个Java类中有哪些接口,构造器,方法,类名,属性等等。

    Class类只能由JVM来创建。而且每个Java类只能对应有一个Class实例,这个实例里面就储存了这个Java类的特性:接口是什么,父类是什么,有哪些方法等待。

    在以下所有的例子中,我们需要用到一个叫Person的类,具体如下:

    public class Person {
    
        //有名字和年龄
        private String name;
        private int age;
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        public int getAge() {
            return age;
        }
        
        public void setAge(int age) {
            this.age = age;
        }
        //无参构造器
        public Person(){}
        //有参构造器
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        //一个私有方法
        private void secretMethod(){}
        
    }
    

    如何使用Class类

    获取到JVM已经创建好的Class类有三种方法:

    1.利用类获取

        Class clazz1 = Person.class;
    

    2.利用全类名获取

        Class clazz2 = Class.forName("com.reflection.Person");
    

    3.利用实例来获取

        Class clazz3 = person.getClass();
    

    这种方法往往用于你并不具体知道这个对象是个什么类的情况下,例如:

        //这里只是举例,如果从外部传入就不一定知道是Person类了
        Object person = new Person();
        Class clazz2 = person.getClass();
        System.out.println(clazz3);
    

    输出结果:正确获取了具体类


    在这里插入图片描述

    现在,你拿到的这个叫clazz的对象,是一个类型为Class的,储存了Person这个类的信息的对象,你可以通过使用clazz,利用反射操作,查看和修改Person类的属性和方法了。

    接下来我们可以先用这个clazz对象来创建一个Person实例对象了!
    利用newInstance()方法:

        Object obj = clazz1.newInstance();
        System.out.println(obj);
    

    输出结果:

    在这里插入图片描述
    obj就是个Person类,成功创建!
    这里需要注意一下,newInstance()方法其实是调用的Person类的无参构造器,所以如果Person类里没有写无参构造器,这里就会失败。

    JVM只会给每个Java类创建一个Class实例,我们可以比较刚才三种获取Class方式得到的对象的地址来验证这一点:

        System.out.println(clazz1==clazz2);
        System.out.println(clazz2==clazz3); 
    

    输出结果:


    在这里插入图片描述

    三类加载器

    类加载器(ClassLoader),就是把类加载到JVM里用的。当JVM启动的时候,Java会按以下顺序自顶而下地启动三类加载器并加载相应的类,而且他们依次呈继承关系:

    1.引导类加载器(Bootstrap):用C++编写,是JVM自带的类装载器,负责Java负责Java平台核心库,==用来装载核心类==(比如Object类,String类等)。由于引导类加载器涉及到虚拟机本地实现细节,所以不允许直接通过引用进行操作,==不可被直接访问==。

    2.扩展类加载器(Extension):
    由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的
    负责将 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中。开发者可以直接使用标准扩展类加载器。

    3.系统类加载器(System):
    由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的
    将==用户写的类==加载到内存中。开发者可以直接使用系统类加载器。

    用代码验证继承关系:

    
            //获取系统类加载器
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();
            System.out.println(classLoader);
            
            //获取系统类加载器的父类----扩展类加载器
            System.out.println(classLoader.getParent());
            
            //获取扩展类加载器的父类----引导类加载器
            System.out.println(classLoader.getParent().getParent());        
    

    输出结果:

    在这里插入图片描述
    最后一个输出是null,因为引导类加载器是无法被直接访问的。

    现在再试着验证什么加载器加载什么类:

        //测试自己写的类是哪个加载器加载的
        System.out.println(Person.class.getClassLoader());
            
        //测试JDK提供的类由哪个加载器负责
        System.out.println(Class.forName("java.lang.Object").getClassLoader());
    

    输出结果:

    在这里插入图片描述
    null仍然是因为引导类加载器无法被直接访问。

    反射

    能够分析类的能力的程序(如Method方法和Field方法),被称为反射

    反射是Java被视为动态语言的关键,允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作对象的内部属性和方法。借助反射,能在设计或运行中添加新类时,有快速地应用开发工具动态地查询新添加类的能力

    如果说Class是对一个类的描述,那么我们可以用Field,Method和Constructor等反射方法来具体描述这个类,得到具体的属性,方法,或者构造器。

    Method

    Method是用来描述一个类中的方法的

    获取Class对应类"所具有的方法"

    1.利用getMethods()方法,虽然无法获取==private方法==,但能把==继承到的方法==全部获取到:

            //clazz1还是上文中的Person类对应的Class实例
            Method[] methods1 = clazz1.getMethods();
            for(Method method:methods1)
            {
                System.out.println("All:"+method);
            }
    

    输出结果:没用Person中那个被设为private的secretMethod(),但是有继承了Object类的toString()equals()等方法。

    在这里插入图片描述

    2.通过getDeclaredMethods()获取,可以获取私有方法,但是无法获取继承方法。

        Method[] methods2 = clazz1.getDeclaredMethods();
        for(Method method:methods2)
        {
            System.out.println("Declared:"+method);
        }       
    

    输出结果:


    在这里插入图片描述

    获取指定方法和执行方法

    获取指定方法getDeclaredMethod:用名字和形参列表来唯一确定 (注意这里方法结尾没用 s )

            Method method3 = clazz1.getDeclaredMethod("setAge", int.class);
            //int不是类,但是int.class就是个Class类型的对象
            //这里注意int和Integer自己斟酌怎么选用
            System.out.println("Now I get :"+method3);
    

    输出结果:


    在这里插入图片描述

    //执行方法invoke:需要传入目标实例和参数。

        Person object = (Person) clazz1.newInstance();
        //给object对象使用method3(setAge)方法,将age设置为10
        method3.invoke(object, 10);
        System.out.println(object.getAge());
    

    输出结果:

    在这里插入图片描述
    成功修改。

    Field

    Field中封装了该类中的属性。

    获取Class对应类"所具有的属性"

    1.使用getFields()方法,获取可访问的属性。

        Field[] field1 = clazz1.getFields();
        for(Field field:field1)
        {
            System.out.println("All:"+field);
        }
    

    由于Person中的两个属性都是private,所以什么都不输出,因为Field[] 里是空的。

    2.使用getDeclaredFields()访问具体的属性,可以访问到私有属性。

            Field[] field2 = clazz1.getDeclaredFields();
            for(Field field:field2)
            {
                System.out.println("Declared:"+field);
            }
    

    输出结果:


    在这里插入图片描述

    成功获取到age属性和name属性。

    获取和修改指定的属性

    通过getDeclaredField("属性名")获得某属性:

            Field field3 = clazz1.getDeclaredField("name");
            System.out.println(field3);
    

    输出结果:


    在这里插入图片描述

    通过get(目标对象)获取指定对象的某属性的值:

            //先创建一个对象
            Person tommy = new Person("Tommy",18);
            //私有变量先设置成可以访问的
            field3.setAccessible(true);
            System.out.println(field3.get(tommy));
    

    输出结果:


    在这里插入图片描述

    通过set(目标对象,新设置的属性的值)来修改属性的值:

        field3.set(tommy, "Jack");
        System.out.println(tommy.getName());
    

    输出结果:


    在这里插入图片描述

    注意,通过这种反射方式修改属性是不会调用相应的set方法的。

    利用反射获取父类信息

    对于一个class对象,可以利用getSuperclass()来获取父类对象的Class实例。
    我们看这样一个例子:

    有三个类:Grandpa,Father,Son。其中Son继承Father,Father继承Grandpa。
    在Grandpa类里有一个私有的属性private int grandpaMoney = 1000。我们目前用反射得到了Son类的Class实例,但现在希望借此来实例化一个Grandpa类,并修改grandpaMoney为2000。

    代码思路如下:

            //初始条件
            Son son = new Son();
            Class sonClass = Son.class;
            //开始利用反射
            Class clazz = sonClass
            //如果在当前类找不到这个属性,就一直向上知道Object类
            for(;clazz!=Object.class; clazz=clazz.getSuperclass())
            {
                //获取全部属性
                Field[] myField = clazz.getDeclaredFields();
                //遍历该类中的属性
                for(Field f:myField)
                {
                    //如果名字是那个,就开始修改
                    if(f.getName().equals("grandpaMoney"))
                    {
                        //由于是私有的,设置为可修改
                        f.setAccessible(true);
                        //利用反射实例化一个对象
                        Object obj = clazz5.newInstance();
                        //修改
                        f.set(obj, 2000);
                        /任务完成!
                    }
                }
            }
    

    相关文章

      网友评论

        本文标题:Java反射基本知识

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