• 2004-02-11

    Java路漫漫

    Tag:向上走

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/dreamhead-logs/87090.html

    自以为对Java中基本的内容了如指掌,今天所发生的一切告诉我,Java路漫漫啊!

    在我们的项目中使用了大量的枚举类。众多枚举类的最初雏形来自当年利用Castor做XML到对象映射时生成那些枚举类。

    每次写一个新的枚举类,基本上都是把一个写好的枚举类复制过来,把枚举项改一改就告完工。久而久之就产生了大量的重复代码。以我通常的习惯,如果重复代码必然会遭到我的当头一击,可偏偏这些枚举类得到了我的纵容。于是,添加新的枚举类成了真正的体力活。

    终于,我幡然省悟,决心治理这些毒瘤。

    处理这种重复类,最好的办法就找出公共部分,抽出一个公共基类,说干就干。

    枚举的应用很广泛,所以有很多值得借鉴的解决方案。我也找到了两个帮手,它们是JDK源码中的java.util.logging.Level和Apache Avalon项目的org.apache.avalon.framework.Enum。

    很快,一个枚举基类完成了,其基本的几个字段是这样的:
    private static List memberList = new ArrayList();
    private static int counter = 0;

    private final String name;
    private final int value;
    private final String resourceBundleName;

    其中name是这个枚举的一个标识,value代表着一个整数值,在C中枚举都对应着一个整数值,resourceBundleName可以用来实现国际化。
    memberList用来存放具体某个枚举类的所有枚举值的内容,通过这种方式,可以是对枚举值的遍历,或是根据name或是value构造一个枚举值的工厂方法。counter是一个计数器,其作用就是在不设置枚举值的value的情况下,下一个值的value比上一个值大1,这是一种模仿C中枚举性质的实现方式。

    编写简单的测试,运行,没问题,心情不错,吃午饭去!

    下午重新打开这个基类,突发奇想:
    对于不同的具体枚举类,memberList应该不同,为什么?想啊!static字段应该是对于每个类来说只有一个,所以,不同的具体类应该有不同memberList。写个测试验证一下。
    测试不通过?怎么会?
    嗡!大脑短路!

    仔细想想,bingo!
    static字段对每个类只有一个确实不假,但问题是这个类是谁?我之前的理由中,犯了一个很低级的错误,认为这个应该是子类,出现这种认识的一个原因在于我认为继承的时候,static字段也应该继承到这个类中来。

    这个类应该是基类!
    从类加载的角度分析一下,一个类加载的时候会把和类相关的所有的内容放入到方法区中,与类相关的内容其中就包括static字段,类的对象通过一个地址定位这个方法区。如果涉及到对类的内容进行访问就通过这个地址找到方法区中自己类型相关的信息。
    子类加载的前提是它的基类已经加载,而基类加载的时候,通常不知道子类的信息,如同我们编写一个基类的时候,并不知道将来会有哪些类继承这个类一样。所以,它会把所有类信息放到它自己的方法区。
    就上面枚举基类而言,memberList一定在这个枚举基类的方法区中,而并不会出现在子类的方法区。
    从语法的角度来看,static变量我们可以通过类来访问,换句话说,我们可以在没有子类的情况下对基类的static字段进行访问,如果每个子类拥有一份memberList,那么通过基类访问的会是哪个呢?所以,显然不可能。

    要实现每种子类都有自己memberList,别无它法,只有在每个具体的子类中声明各自memberList。

    突然想起Avalon中Enum的实现,它有一个构造函数是这样的
    protected Enum( final String name, final Map map )
    其中的map所起的作用等同于memberList。
    开始的时候,我还在笑话Enum实现的笨拙,现在彻底明白了,这个构造函数的作用就是让基类在完成构造对象之后,把枚举值放入这个存储之中。五十步笑百步,迂腐!

    或许看官们会奇怪,我的程序如何通过自己的测试。这一方面有自己测试不完整的原因,另外一方面,虽然在原来的实现中memberList对于所有的子类只有一个,因为每次插入的时候,都是一个简单的add,它会把这个对象插入到memberList的最后,而memberList add方法的参数是Object,所以,插入的内容即便错误也不会发现。强烈期待咖啡猛虎(JDK 1.5,代号Tiger)的到来,至少新增的泛型语法可以减少犯下这种错误的可能性。
    查找的时候,只要两个枚举值的name不同,就没有问题,而恰恰我的测试中就没有相同name的枚举值。
    TDD和重构都在强调测试的重要,经过我的实践,证明了一个道理,测试在用以证明别人的正确性之前,必须保证自己的正确性。

    不怕出问题,就怕找不到问题的根本原因。找到问题的原因,改起来还是很快的。有一点不可否认,改过的代码因为每个子类都要有自己的memberList,所以从代码效果上来看,明显不如原来的代码那么优雅。
    没办法,谁让static是OO中不大不小的一个瑕疵呢!

    JDK 1.5中引入了一些新的语法,其中就有这里提及的enum,有了真正的enum,我的枚举基类就算彻底的Game Over了,换句话说,这个枚举基类可是一个真正的费力不讨好的东西。不过这只咖啡猛虎小荷才露尖尖角,等到实际应用还要好长的一段路要走,所以这个枚举基类还是可以苟延残喘些日子。

    Andrew Koenig和Barbar Moo夫妇在《C++沉思录》中提出的观点,“语言设计就是库设计,库设计就是语言设计”,对于语言本身良好运用可以极大提高程序的质量,随着自己实践的增多,越发可以体会到这句话的真谛了。

    Java路漫漫其修远兮,吾将上下而求索!

    分享到:
    引用地址: