Java语言十五讲(第三讲 Reflection)

 

写在前面:希望大家可以踊跃发言,大家阅读后有什么感受或者心得体会以及建议都可以在下方留言的,根据你的留言,郭老师会及时调整讲解内容做一些优化和改进,得不到大家的反馈,不知道讲解是否有问题,是否符合大家的口味以及能否帮助到大家

 

知识点之间是有逻辑关联的,配合在一起构成一个体系。讲了前两次,大家都发觉了会用到一个共同的知识点,叫Reflection反射。那么今天我就来给大家讲讲这个Reflection,也有好几个同学提出希望进一步了解一下。

这个词的中文翻译不是很好,不容易从字面体会到含义。按照英文词典的解释,Reflection的常用的一个意思是the image of something in a mirror(映照出的影像)。人拿着镜子干什么?是看自己。对,反观自身,就是Reflection的本质。它是Java语言的一个特性,允许一个执行的程序检查或"introspect"(内窥)自身或者别的类,并操纵里面的方法和属性。

初看起来,一个运行程序内窥自身,似乎没什么用处,因为一般的理解,程序的作用就是处理外部数据的,只要有功能做一些有用的事情就够了,不需要了解自己。

不过有的场景中,很需要这样的功能。比如在一个图形开发环境中,引入了组件,如何知道组件里面有什么属性和方法?开发环境就需要用Reflection来识别这些动态引入进来的组件了。还有,我们要做一个框架,比如做Tomcat或者Spring,里面要管理很多对象,自然就需要知道这些对象的内部结构。我十几年前做的R-O Mapping就是用的Reflection机制构建起来的。对于写工具写框架平台的程序员来讲,没有Reflection,都不知道该怎么编程序了。

好,我们先看Reflection的基本使用,代码如下(SimpleReflection.java):

package com.test;
import java.lang.reflect.Method;

public class SimpleReflection {
    public SimpleReflection() {
    }
    public int doSomething() {
        return 0;
    }
    public static void main(String[] args) {
        Class<?> clz;
        try {
            clz = Class.forName("com.test.SimpleReflection");
            Method m[] = clz.getDeclaredMethods();
            for (int i = 0; i < m.length; i++) {
                System.out.println(m[i].toString());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

SimpleReflection是一个简单的程序,有一个构造函数,一个doSomething(),一个main()。main()里面用class.forName()加载自身这个类,通过clz.getDeclaredMethods()找到定义的所有方法,打印出来,运行结果如下:

public static void com.test.SimpleReflection.main(java.lang.String[])
public int com.test.SimpleReflection.doSomething()

我们看到了,会把main()和doSomething()打印出来,构造函数没有,它不是method,是单独处理的,我们后面会讲到。

这就是Reflection基本的使用。关键是用到了ClassObject(类对象)。前面我们讲到过,在JVM加载类的时候,把类的定义写到方法区,同时,生成一个class object,通过它获取类信息,现在我们就用到了。

我们再深究一下,增加私有方法定义,增加属性定义,增加构造函数,看看怎么获得。这些都是有现成办法的。

我们扩展一个上面的程序,另外取名SimpleReflection2。先增加一个私有方法

realDoSomething():
    private int  realDoSomething() {
        return result;
    }

运行结果是:

public static void com.test.SimpleReflection2.main(java.lang.String[])
public int com.test.SimpleReflection2.doSomething()
private int com.test.SimpleReflection2.realDoSomething()

结果一样出来了,说明clz.getDeclaredMethods()是找到的所有方法,并且私有方法一样可以拿来调用,后面我们会讲到。

我们再看怎么拿到构造函数。在main()里面,增加下面这一段:

  Constructor<?>[] cts = clz.getConstructors();
    for (int i = 0; i < cts.length; i++) {
        System.out.println(cts[i].toString());
    }

运行结果打印出了publiccom.test.SimpleReflection2(int)

再进一步到获取属性,增加下面这一段:

 Field[] flds = clz.getDeclaredFields();
       for (int i = 0; i < flds.length; i++) {
             System.out.println(flds[i].toString());
       }

运行之后,把我们定义的两个属性都打印出来了。

int com.test.SimpleReflection2.result
private int com.test.SimpleReflection2.temp

好,我们到此就分别获取了一个类里面的方法属性和构造函数,部分public还是private都能获取到了。我们把上面的代码合在一起,得到完整的代码,SimpleReflection2.java:

package com.test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class SimpleReflection2 {
    int result = 1;
    private int temp = 100;

    public SimpleReflection2(int result) {
        this.result = result;
    }
    public int doSomething() {
        return realDoSomething();
    }
    private int  realDoSomething() {
        return result;
    }
    public static void main(String[] args) {
        Class<?> clz;
        try {
            clz = Class.forName("com.test.SimpleReflection2");
            Method m[] = clz.getDeclaredMethods();
            for (int i = 0; i < m.length; i++) {
                System.out.println(m[i].toString());
            }
            Constructor<?>[] cts = clz.getConstructors();
            for (int i = 0; i < cts.length; i++) {
                System.out.println(cts[i].toString());
            }
            Field[] flds = clz.getDeclaredFields();
            for (int i = 0; i < flds.length; i++) {
                System.out.println(flds[i].toString());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行一下,结果出来了:

public static void com.test.SimpleReflection2.main(java.lang.String[])
public int com.test.SimpleReflection2.doSomething()
private int com.test.SimpleReflection2.realDoSomething()
public com.test.SimpleReflection2(int)
int com.test.SimpleReflection2.result
private int com.test.SimpleReflection2.temp

可以看到,SimpleReflectrion2里面的main(),构造函数,方法,属性都打印出来了。

事情到此就完了吗?没有呢。还有很多情况我们没有考虑,比如父类子类继承关系,比如Annotation,比如实现interface,比如调用方法。

稍安勿躁,一步步来,我们一个一个讲解。小时候看电视剧《聪明的一休》,看到半截,总是有一句话:休息一会儿休息一会儿。

 

或许我们没有意识到,上面的类就包含了父子关系。按照Java的对象体系,有一个根对象Object,所以上述SimpleReflection2也是Object的一个子类。

我们来看怎么获取到父类的信息。Class里面有一个方法叫getSuperclass(),它就能获得当前类的父类,对SimpleReflection2而言,它的父类就是Object。我们试一下:

Method m[] = clz.getSuperclass().getDeclaredMethods();
for (int i = 0; i < m.length; i++) {
       System.out.println(m[i].toString());
}

打印结果如下:

protected void java.lang.Object.finalize() throws java.lang.Throwable
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
private static native void java.lang.Object.registerNatives()

确实是把父类Object里面的方法打印出来了。

事实上,还有一个方法,可以直接获得从父类继承下来的方法,就是getMethods(),我们试验一下,将上面的getDeclaredMethods()改成getMethods(),运行结果:

public static void com.test.SimpleReflection2.main(java.lang.String[])
public int com.test.SimpleReflection2.doSomething()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

这个方法把SimpleReflection2里面的public方法以及从父类继承下来的方法都找出来了。相比之下,getDeclaredMethods()是只找到本类自己定义的方法,不管是public还是private,都会找出来。

 

我们再看Package,Annotation和Interface的情况,Class还是一样提供了直接的方法。直接上代码。

先定义一个interface, 代码如下(BaseInteface.java):

package com.test;
public interface BaseInterface {
    public int interfaceInt=0;
    void method1();
    int method2(String str);
}

interface里面包含了属性interfaceInt和方法method1()以及method2()。

再定义一个基础类,代码如下(BaseClass.java):

package com.test;
public class BaseClass {
    public int baseInt;

    private static void method3(){
        System.out.println("Method3");
    }
    public int method4(){
        System.out.println("Method4");
        return 0;
    }
    public static int method5(){
        System.out.println("Method5");
        return 0;
    }
    void method6(){
        System.out.println("Method6");
    }
    // inner public class
    public class BaseClassInnerClass{}
}

基础类里面包含了属性baseInt以及方法method3(),method4(),method5(),method6(),静态的和非静态的,还包含一个内部类BaseClassInnerClass。

接着我们写一个具体的类,继承BaseClass并实现BaseInterface,代码如下(ConcreteClass.java):

package com.test;
public class ConcreteClass extends BaseClass implements BaseInterface {
    public int publicInt;
    private String privateString="private string";
    protected boolean protectedBoolean;
    Object defaultObject;

    public ConcreteClass(int i){
        this.publicInt=i;
    }
    @Override
    public void method1() {
        System.out.println("Method1 impl.");
    }
    @Override
    public int method2(String str) {
        System.out.println("Method2 impl.");
        return 0;
    }
    @Override
    @Deprecated
    public int method4(){
        System.out.println("Method4 overriden.");
        return 0;
    }
    public int method5(int i){
        System.out.println("Method4 overriden.");
        return 0;
    }
    // inner classes
    public class ConcreteClassPublicClass{}
    private class ConcreteClassPrivateClass{}
}

这个具体的类,继承了BaseClass,实现了BaseInterface,还有自己额外的方法和属性以及内部类,方法还有用@Override 和@Deprecated annotation的情况。这算是一个综合的例子了,代码如下(TestConcreteClass.java):

package com.test;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;

public class TestConcreteClass {
    public static void getClassObj() {
        Class<?> concreteClass = ConcreteClass.class;
        concreteClass = new ConcreteClass(5).getClass();
        try {
            concreteClass = Class.forName("com.test.ConcreteClass");
            System.out.println(concreteClass.getName()); 
        } catch (ClassNotFoundException e) {
        }
    }
    public static void getSuperClass() {
        Class<?> superClass;
        try {
            superClass = Class.forName("com.test.ConcreteClass").getSuperclass();
            System.out.println(superClass); 
        } catch (ClassNotFoundException e) {
        }
    }
    public static void getInterfaces() {
        Class<?>[] interfaces;
        try {
            interfaces = Class.forName("com.test.ConcreteClass").getInterfaces();
            System.out.println(interfaces[0]); 
        } catch (ClassNotFoundException e) {
        }
    }
    public static void getDeclaredClasses() {
        Class<?>[] explicitClasses;
        try {
            explicitClasses = Class.forName("com.test.ConcreteClass").getDeclaredClasses();
            System.out.println(Arrays.toString(explicitClasses));
        } catch (SecurityException e) {
        } catch (ClassNotFoundException e) {
        }       
    }
    public static void getPackageName() {
        try {
    System.out.println(Class.forName("com.test.BaseInterface").getPackage().getName());
        } catch (ClassNotFoundException e) {
        }
    }
    public static void getClassModifier() {
        System.out.println(Modifier.toString(ConcreteClass.class.getModifiers())); 
        try {
    System.out.println(Modifier.toString(Class.forName("com.test.BaseInterface").getModifiers()));
        } catch (ClassNotFoundException e) {
        }
    }
    public static void getAllAnnotations() {
        java.lang.annotation.Annotation[] annotations;
        try {
            Method[] methods;
            methods = Class.forName("com.test.ConcreteClass").getMethods();
            for (int i = 0; i<methods.length; i++) {
                annotations = methods[i].getAnnotations();
                for (int j = 0; j<annotations.length; j++) {
                    System.out.println(methods[i]+":"+annotations[j]);
                }
            }
        } catch (ClassNotFoundException e) {
        }
    }
    public static void main(String[] args) {
        getClassObj();
        getSuperClass();
        getInterfaces();
        getDeclaredClasses();
        getPackageName();
    getClassModifier();
        getAllAnnotations();
    }
}

上面的代码分别获取类名,父类,接口,子类,包名,类修饰以及注解。

运行结果如下:

com.test.ConcreteClass
class com.test.BaseClass
interface com.test.BaseInterface
[class com.test.ConcreteClass$ConcreteClassPrivateClass, class com.test.ConcreteClass$ConcreteClassPublicClass]
com.test
public
public abstract interface
public int com.test.ConcreteClass.method4():@java.lang.Deprecated()

上面的代码很简单,都是直接调用API,没有什么特别的技巧,大家自己看自己试。

运行结果里面唯一需要提及的是最后的Annotations,我们其实定义了两个,一个是@Override,一个是@Deprecated,实际上我们这儿只得到了一个。这不是错误。原因是因为@Override是RetentionType是SOURCE,作用于源代码级别的,在编译时起作用,.class文件中不再保留,因此现在运行时是得不到了。这个结果辅佐证明了我们在Annotation讲座里面的结论。

接下来,我们进一步使用Reflection进行操作,调用类里面的方法,代码如下(Calc.java):

import java.lang.reflect.Method;

public class Calc {
    public Calc() {
    }
    public int add(int a, int b) {
        return a + b;
    }
private int times(int a, int b) {
        return a * b;
    }

    public static void main(String[] args) {
        Class<?> cls2;
        try {
            cls2 = Class.forName("com.test.Calc");

            Class<?> partypes[] = new Class[2];
            partypes[0] = Integer.TYPE;
            partypes[1] = Integer.TYPE;
            Method meth = cls2.getMethod("add", partypes);

            Calc methobj = (Calc) cls2.newInstance();
            Object arglist[] = new Object[2];
            arglist[0] = new Integer(37);
            arglist[1] = new Integer(47);
            System.out.println(meth.invoke(methobj, arglist));

            meth = cls2.getDeclaredMethod("times", partypes);
            System.out.println(meth.invoke(methobj, arglist));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

可以看出,类名,方法名,都是用字符串得方式传入的,进行的动态加载和调用。注意了我们这里调用了一个私有方法times()(获取私有方法的办法是getDeclaredMethod而不是getMethod)。
运行结果自然是正确的84和1739。
有了reflection,私有方法和属性也能访问,这是千真万确的!如果另外一个类来访问,要在调用之前执行一句 method.setAccessible(true);以获取授权。
我们一般讲私有方法和属性只能在本类访问,那是对一般场景而言。其实Java在这里给出了别的后门,能让你做一般情况下做不到的事情。
当然,我不是说别的书上的介绍是错的,对给一般学生讲的教科书上,知道private的含义是很有必要的。我只是想说,读书要能破书,华裔诺贝尔物理奖得主丁肇中博士说过:什么叫读懂了?就是你把书中的定律都推翻了,这就是读懂了。博士的意思就是强调任何物理学定律都有其适用范围和局限。真正读懂就是了解了这些适用范围并能进一步发展它。

Reflection执行程序这种办法会降低性能,这个也是要心中有数的。我们试验一下,在上面的调用语句之后增加一段代码,进行10000次调用,比较看看:

        Object retobj;
        int i = 0;
        long lstart1 = System.currentTimeMillis();
        while (i++ < 10000) {
            retobj = meth.invoke(methobj, arglist); // calc.add()
        }
        long lend1 = System.currentTimeMillis();
        System.out.println(lend1 - lstart1);

        Integer retInt;
        int j = 0;
        long lstart2 = System.currentTimeMillis();
        while (j++ < 10000) {
            retInt = methobj.add(new Integer(37), new Integer(47));         
        }
        long lend2 = System.currentTimeMillis();
        System.out.println(lend2 - lstart2);

上面的代码分别看Reflection调用和直接调用之间的效率差异。再次运行,时间差分别是48和16(肯定依据不同的机器有不同的数据,但是这个时间差之间的对比是大体如此的。)可见性能还是有差异的。现代JVM比较优化了,早期的有一个数量级的差异。更加离谱的是,Microsoft模仿Java做了一个.NET,它里面对Reflection的实现竟有两个数量级的差异(继承Sun公司的光荣传统,顺便黑Microsoft一下^_^)。

Reflection最常用方式的就是根据字符串名称找到类,加载它,根据字符串名字调用里面的方法。这样,很多任务都可以写在外面文件中进行配置,不用事先知道它们,这就是框架运行的基础。即便在大的应用软件中,也会常用这个技术,用来支持用户的客户化扩展。比如,有些事件的处理逻辑,不想写死在代码中,就可以制订一个规范,让用户自己定制扩展类,然后把类名和相关的方法名注册在系统中,运行时动态加载和调用。这个技术为应用软件带来了很大的灵活性。

到此还是对Reflection的一些基本使用。实际上,我们稍微往前再走一步,就能变出一些惊艳的魔术。
我们看上面的例子,都用到了Class.forName()和Reflection的API,类名方法名都是写死在示例代码中的,大家可能还不觉得有什么。我们可以试着把这些名字配置到外部文件中,一下子就感觉不一样了。
如,我们给一个配置文件,如下(beans.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <bean name="hello" class="com.test.HelloWorld">
    <property name="words" value="Hello World!"></property>
  </bean>

</beans>

等等等等。上面的文件好像很眼熟啊!是的,用过Spring的程序员都不陌生。通过这个XML文件,我们声明了有这么一个类HelloWorld,程序对外打印的内容也是通过属性words配置的。我们也把这些叫做bean。
我们可以用一个BeanXMLReader程序,读取上面的bean信息,保存在定义表中,代码类似:

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();
BeanRegistry registry = reader.getDefinitionFromXML("beans.xml");

然后用一个BeanFactory,根据上面的bean的定义表,通过Reflection加载并实例化,代码类似:

    BeanFactory beanfactory = new AutowireBeanFactory();
    for (Map.Entry<String, BeanDefinition> defEntry : registry.entrySet()) {
        beanfactory.registerBean(defEntry .getKey(), defEntry .getValue());
    }

beanfactory.registerBean()通过Class.forName()以及newInstance()就可以加载和实例化一个bean对象保存起来。

以后的调用就可以通过factory拿到bean并调用,代码类似:

    HelloWorld hello = (HelloWorld) beanfactory.getBean(name);
    hello.say();

到这里,我们忽然发现,这不就是实现了一个类似于Spring核心的东西吗?是的,原理上是。每当我讲到这里的时候,总有几个学生瞪大了眼睛。一步一步地,我们也能开始理解和欣赏巨人们做出来的东西了,这是一种很愉悦的感觉。我这里不详细展开了,我会有另一系列讲座介绍如何自己做出一个小的Spring框架来。
学习是一个有趣的过程,很像爬山。在爬山的路上,慕然回首,会惊讶我们竟然走了这么远!如果坚持不懈继续往上爬,定会达到“会当凌绝顶,一览众山小”的境界。

对Reflection的介绍就可以先到这里了。其实没有什么难的,可能大家只是以前用得不多有一些陌生感。而从语言的角度,技术界对Reflection的评价相当高,以前别的语言是没有这样的机制的,程序能识别程序本身,通过这样的手段,软件的灵活性动态性扩展性都提到了一个崭新的层面,甚至为未来留下许多遐想。

从技术哲学的角度扩展开了去看,生物进化的过程,一开始都只是本能的具有某种生化功能,就在若干万年前,就不知道怎么的忽然有了自我意识,生物开始认识自己了,这个意识最后令人惊讶地产生了文明。由此而知,Reflection是第一步,只有认识自己,才会走向智慧。我猜测可以进一步结合Java的一些别的特性,如ClassLoader实现同一个程序的多版本迭代,实现一边处理数据一边根据大数据调整程序自身,再结合神经网络进行进化演变,构建一个智慧的动态系统,最后或许肯定可以达到真正的人工智能。我们可以从这里体会到Reflection的潜在的强大力量,再一次震撼于Java创始者的深邃。二十几年前,领会到了这些后,我猜测,James Gosling一定是这么构想的,于是掷笔仰天长笑:我窥见了上帝的大设计。
古希腊Delphi的阿波罗神庙上刻着箴言:γνῶθι σεαυτόν(认识你自己)。

 

 

上一篇:C# 反射(Reflection)


下一篇:Lecture04_转换控制_GAMES101 课堂笔记——2020.2.21