UML软件工程组织

再谈在Java中使用枚举(二)
原作者John I. Moore, Jr 编译张劲松,贾晓兰 来 源: 赛迪网
一个实现枚举的微型语言(AMini-Language for Enums)

假如我发明一种枚举专用的微型语言(且叫它jEnum),它专门用来申明枚举。然后我再用一个特殊的"翻译"程序将我用这种语言定义的枚举转化为对应的"对象枚举"定义,那不是就解决了"对象枚举"定义复杂的问题了吗。当然我们很容易让这个"翻译"程序多做一些工作。比如加入Package申明,加入程序注释,说明整数值和该对象的字符串标签名称等等。让我们看下面这样一个例子:

package com.softmoore.util;
/**
  * Various USA coins
  */
enum Coin { PENNY("penny") = 1, NICKEL("nickel") = 5, DIME("dime") = 10,
            QUARTER("quarter") = 25, HALF_DOLLAR("half dollar") = 50 };


虽然"整数常数枚举"在有些情况下优点比较显著。但是总体上讲"对象枚举"提供的类型安全还是更为重要的,相比之下哪些缺点还是比较次要的。下面我们大概讲一下jEnum,使用它我们又可以得到紧凑和有效的枚举申明这一特点,也就是我们前面提到的条件2。

熟悉编译器的朋友可能更容易理解下面这一段jEnum微型语言。

compilationUnit = ( packageDecl )? ( docComment )? enumTypeDecl .
packageDecl     = "package" packagePath ";" .
packagePath     = packageName ( "." packageName )* .
docComment      = "/**" commentChars "*/" .
enumTypeDecl    = "enum" enumTypeName "{" enumList "}" ";" .
enumList        = enumDecl ( "," enumDecl )* .
enumDecl        = enumLiteral ( "(" stringLiteral ")" )? ( "=" intLiteral )? .
packageName     = identifier .
enumTypeName    = identifier .
enumLiteral     = identifier .
commentChars    = any-char-sequence-except-"*/"


这种语法允许在开始申明package,看起来和Java语言还挺像。你可以增加一些javadoc的注解,当然这不是必须的。枚举类型的申明以关键字"enum"开头,枚举的值放在花括号中{},多个值之间用逗号分开。每一个值的申明包括一个标准的Java变量名,一个可选的字符串标签,可选的等号(=)和一个整数值。

如果你省略了字符串标签,那么枚举的变量名就会被使用;如果你省略了等号和后面的整数值,那么它将会自动按顺序给你的枚举赋值,如果没有使用任何数值,那么它从零开始逐步增加(步长为1)。字符串标签作为toString()方法返回值的一部分,而整数值则作为ord()方法的返回值。如下面这段申明:

enum Color { RED("Red") = 2, WHITE("White") = 4, BLUE };


  • RED 的标签是 "Red",值为 2 ;

  • WHITE的标签是"White",值为4;

  • BLUE的标签是"BLUE" ,值为5 。

    要注意的是在Java中的保留字在jEnum也是保留的。比如你不可以使用this作为package名,不可以用for为枚举的变量名等等。枚举的变量名和字符串标签必须是不同的,其整数值也必须是严格向上增加的,象下面这段申明就是不对的,因为它的字符串标签不是唯一的。

    enum Color { RED("Red"), WHITE("BLUE"), BLUE };


    下面这段申明也是不对的,因为WHITE会被自动赋值2 ,和BLUE有冲突。

    enum Color { RED = 1, WHITE, BLUE = 2 };


    下面这是一个具体的实例。它将会被"翻译"程序使用,用以转换成我们枚举申明为可编译的Java源程序。

    package com.softmoore.jEnum;
    /**
      * This class encapsulates the symbols (a.k.a. token types)
      * of a language token.
      */
    enum Symbol {
        identifier,
        enumRW("Reserved Word: enum"),
        abstractRW("Reserved Word: abstract"),
        assertRW("Reserved Word: assert"),
        booleanRW("Reserved Word: boolean"),
        breakRW("Reserved Word: break"),
        byteRW("Reserved Word: byte"),
        caseRW("Reserved Word: case"),
        catchRW("Reserved Word: catch"),
        charRW("Reserved Word: char"),
        classRW("Reserved Word: class"),
        constRW("Reserved Word: const"),
        continueRW("Reserved Word: continue"),
        defaultRW("Reserved Word: default"),
        doRW("Reserved Word: do"),
        doubleRW("Reserved Word: double"),
        elseRW("Reserved Word: else"),
        extendsRW("Reserved Word: extends"),
        finalRW("Reserved Word: final"),
        finallyRW("Reserved Word: finally"),
        floatRW("Reserved Word: float"),
        forRW("Reserved Word: for"),
        gotoRW("Reserved Word: goto"),
        ifRW("Reserved Word: if"),
        implementsRW("Reserved Word: implements"),
        importRW("Reserved Word: import"),
        instanceOfRW("Reserved Word: instanceOf"),
        intRW("Reserved Word: int"),
        interfaceRW("Reserved Word: interface"),
        longRW("Reserved Word: long"),
        nativeRW("Reserved Word: native"),
        newRW("Reserved Word: new"),
        nullRW("Reserved Word: null"),
        packageRW("Reserved Word: package"),
        privateRW("Reserved Word: private"),
        protectedRW("Reserved Word: protected"),
        publicRW("Reserved Word: public"),
        returnRW("Reserved Word: return"),
        shortRW("Reserved Word: short"),
        staticRW("Reserved Word: static"),
        strictfpRW("Reserved Word: strictfp"),
        superRW("Reserved Word: super"),
        switchRW("Reserved Word: switch"),
        synchronizedRW("Reserved Word: synchronized"),
        thisRW("Reserved Word: this"),
        throwRW("Reserved Word: throw"),
        throwsRW("Reserved Word: throws"),
        transientRW("Reserved Word: transient"),
        tryRW("Reserved Word: try"),
        voidRW("Reserved Word: void"),
        volatileRW("Reserved Word: volatile"),
        whileRW("Reserved Word: while"),
        equals("="),
        leftParen("("),
        rightParen(")"),
        leftBrace("{"),
        rightBrace("}"),
        comma(","),
        semicolon(";"),
        period("."),
        intLiteral,
        stringLiteral,
        docComment,
        EOF,
        unknown
    };


    如果对Day的枚举申明存放在Day.enum文件中,那么我们可以将这个文件翻译成Java源程序。

    $ java -jar jEnum.jar Day.enum


    翻译的结果就是Day.javaJava源程序,内容和我们前面讲的一样,还包括程序注释等内容。如果想省一点事,你可以将上面比较长的命令写成一个批处理文件或是Unix,Linux上的shell script,那么以后使用的时候就可以简单一些,比如:

    $ jec Day.enum


    关于jEnum有四点注意事项要说明一下。

    1. 申明文件名不一定后缀为".enum.",其它合法文件后缀都可以。

    2. 如果文件后缀不是".enum.",那么翻译程序将首先按给出的文件名去搜索,如果没有,就假定给出的文件名是省略了".enum."后缀的。像这种命令是可以的:

    $ java -jar jEnum.jar Day


    3. 生成的Java源程序文件名是按照申明文件内的定义得出的,而不是依据申明文件的名称。

    4. 翻译程序还接受以下几个开关

    -o 生成"对象枚举"类枚举,是缺省值

    -c 生成"整数常数枚举"类枚举,用类来实现

    -i 生成"整数常数枚举"类枚举,用接口来实现

    要注意的是,-C开关虽然生成"整数常数枚举",但它同时还提供了一些"对象枚举"中所具有的方法,如first(), last(),toString(int n),prev(int n), 和next(int n)。

    jEnum工具可以从网上自由下载,其地址是:http://www.onjava.com/onjava/2003/04/23/examples/jEnum.zip


  • 版权所有:UML软件工程组织