技术干货

Java反射基本知识

2020-02-17  本文已影响0人  HeyLehr

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);
                    /任务完成!
                }
            }
        }
上一篇下一篇

猜你喜欢

热点阅读