Java基础编程(06)--反射

为了保持Java基础编程系列的完整性, 反射机制还是写一下, 没有什么太难的地方。 在文章的最后与Python进行了一个对比和演示, 用于加深对这两种语言设计本身的理解。

1. 什么是反射

以我目前的理解来看, 反射就是一种在运行时借助Class对象获取一个类所有信息的机制, 包括变量, 构造器以及方法等。 简单的来说就是使用某个产品的说明书, 反向的构造出这个产品, 在构造这个产品的时候, 会发现一些产品所隐藏的信息。

更具体地来讲, 反射能够在运行时判断任意一个对象所属的类, 构造任意一个类的对象, 判断任意一个类所具有的成员变量和方法, 以及调用任意一个对象的方法, 包括私有方法。

那么为什么需要有反射机制? 反射机制在RPC框架下有着非常重要的应用, 此外, 像SpringBoot等Web框架也是基于反射机制而构建的, 并且更功利的讲, EventBus就是用的反射机制来实现观察者模式的。

2. 什么是Class对象

我们知道使用多态机制能够简化代码的复杂度, 使代码更加的灵活, 如下面的代码片段:

interface Animal { void run(); }

class Cat implements Animal{
    @Override
    public void run() { System.out.println("Cat running"); }
}

class Dog implements Animal {
    @Override
    public void run() { System.out.println("Dog running"); }
}

class Square {
    public void game(Animal animal) { animal.run(); }
}

在这里我们只对操作基类(Animal)的引用, 这样一来如果想要对代码进行扩展, 我们可以很轻松的做到, 而不必更改原有的代码结构。 客户端的程序员使用泛化的Animal来调用run方法, 尽管对象被泛化了, 但是依然能够产生正确的行为, 那么这个就是多态机制。

Java多态机制的实现是通过Class对象所实现的, 任何一个Java类都会有一个唯一的Class对象, 保存在与类同名的.class文件中。

3. 获取Class对象

有3种方式可以获取Class对象, 第一个是Class类所提供的forName静态方法, 二是类字面量, 最后一个是Object对象所提供的getClass方法。

3.1 Class.forName

该静态方法接受一个完整的类名, 即包名.类名, 返回一个该类所对应的Class对象, 该方法会抛出ClassNotFoundException, 在没有找到该类的情况下。

Class<?> clazz = Class.forName("chapter21.Reflection.Student");
3.2 类字面量

有时候我们可以直接导入一个类, 例如ArrayList, 那么此时就可以直接使用ArrayList.class来获取Class对象:

Class<?> arrayListClass = ArrayList.class;
3.3 Object.getClass

有时候我们可以拿到一个实例对象, 通过该实例对象也可以获取到Class对象。 该方法是由Object所提供的, 也就是说任意一个对象都可以使用:

String fullName = "SmartKeyerror";
Class<?> stringClass = fullName.getClass();

4. 获取类结构信息

一个类, 通常由成员变量, 构造函数, 成员方法, 静态方法等所组成, Class对象提供了一系的方法来获取这些信息。

首先定义一个测试类:

class Student {
    public static final int ID = 10;

    private String name;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public String toString() { return "Student: " + name; }

    private static void printHello() { System.out.println("hello~"); }
}

通过getDeclaredMethods可以获取所有的方法, 包括私有, 静态以及成员方法:

Class<?> clazz = Class.forName("chapter21.Reflection.Student");
/* 方法返回Method对象所组成的数组 */
Method[] methods = clazz.getDeclaredMethods();
/* 打印所有的方法名称 */
Arrays.stream(methods).forEach(t -> { System.out.println(t.getName()); });

通过getDeclaredFields可以获得所有的变量, 包括私有, 静态以及成员变量:

Field[] fields = clazz.getDeclaredFields();
Arrays.stream(fields).forEach(t -> { System.out.println(t.getName()); });

通过getDeclaredConstructors可以获取所有的构造方法, 包括私有的构造方法:

Constructor[] constructors = clazz.getDeclaredConstructors();
Arrays.stream(constructors).forEach(t -> { System.out.println(t.getName()); });

除了这三个方法以外, 对应的, 还有getMethodsgetFieldsgetConstructors, 这三个方法作用与上面的相同, 只不过无法获取私有的方法, 变量或者是构造器而已。

通过这些方法我们基本上已经能够较为完整的还原出一个类的结构了, 能够知道一个变量是否私有, 名称以及类型; 方法是否私有, 返回类型以及参数类型, 但是无法获取函数的方法体代码, 这个是无法做到的。 以及构造函数的数量和参数类型这些信息。

5. 获取准确信息

从全局的角度来看, 我们能够通过Class对象获取各种方法和各种变量所组成的数组, 同样地, 可以根据一些条件来准确的获取单一的方法或者是变量。

在一个类中, 区分一个函数的两个标志就是函数名称和参数类型(虽然参数类型的顺序也可以用来区分, 但是这属于极差的编码习惯), 同样的, 可以使用这两个信息来准确的找出我们想要的方法:

/* 获取setName方法, 参数为函数名称以及参数类型的Class对象 */
Method setNameMethod = clazz.getMethod("setName", String.class);

/* 获取该类的一个实例, 相当于new Student(), 使用无参构造函数 */
Object instance = clazz.newInstance();

/* 调用该方法, 由于是成员方法, 所以得有个实例 */
setNameMethod.invoke(instance, "smart");

/* 检测方法调用是否成功 */
System.out.println(((Student)instance).getName());

获取字段的准确信息要简单一些, 因为字段名称本就是唯一的:

Field nameField = clazz.getDeclaredField("name");
System.out.println(nameField.getType().getName());

基本上这些就是反射机制的核心内容了, 剩下的就是一些细节的处理, 这些内容可以通过查看javadoc来进行了解。

6. Python中的反射机制

没有对比就没有伤害, Python作为一个面向对象的语言, 与Java一样, 拥有反射机制, 只不过Python中, 大家更乐意称之为特殊方法, 并且获取信息更加的方便。

class Student(object):
    """
    This is Student class
    """
    def __init__(self):
        self.__name = None

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    def __private_method(self):
        print("This is private")

我们可以通过以__开头的一些特殊方法来获取指定类的结构信息, 例如使用Student.__dict__来获取Student的所有方法和一些基本属性:

{
    '__module__': '__main__',
    '__doc__': '\n    This is Student class\n    ',
    '__init__': <function Student.__init__ at 0x7f2d7403b510>,
    'get_name': <function Student.get_name at 0x7f2d722e49d8>,
    'set_name': <function Student.set_name at 0x7f2d722e4950>,
    '_Student__private_method': <function Student.__private_method at 0x7f2d722e4a60>,
    '__dict__': <attribute '__dict__' of 'Student' objects>,
    '__weakref__': <attribute '__weakref__' of 'Student' objects>
}

可以看到Python中的私有方法其实最终会变成_Student__private_method, 我们是可以直接调用这个方法的。

通过实例的__class__方法可以获取实例的类信息:

print(Student().__class__)

<class '__main__.Student'>

由于实例变量是可以动态添加和修改的, 所以获取这个信息没有什么太大意义, Python也没有提供。

两种语言对同一个机制放在一起做对比, 很容易就能发现两者的不同, 以及进一步加深对语言本身的理解。 做对比的主要目的还是想传达自己的一个观点: 编程语言, 在很大程度上都是互通的。

smartkeyerror

日拱一卒,功不唐捐