第34条:用接口模拟可伸缩的枚举

    枚举类型是不可扩展的,但是接口类型是可扩展的。使用接口,可以模拟可伸缩的枚举。

    就几乎所有方面来看,枚举类型都优越于类型安全枚举模式。从表面上看,有一个异常与伸缩性有关,这个异常可能处在原来的模式中,却没有得到语言构造的支持。换句话说,使用这种模式,就有可能让一个枚举类型去扩展另一个枚举类型;利用这种语言特性,则不可能这么做。这绝非偶然。枚举的可伸缩性最后证明基本上都不是什么好点子。扩展类型的元素是基本类型的实例,基本类型的实例却不是扩展类型的元素,这样很混乱。

    类型安全枚举模式:定义一个类来代表枚举类型的单个元素,并且不提供任何公有的构造函数。相反,提供公有的静态final域,使枚举类型中的每一个常量都对应一个域。

public class Suit {
    private final String name;
    
    private Suit(String name){
        this.name = name;
    }
    
    public String toString(){
        return name;
    }
    
    public static final Suit CLIBS = new Suit("clubs");
    public static final Suit DIAMONDS = new Suit("diamonds");
    public static final Suit HEARTS = new Suit("hearts");
    public static final Suit SPADES = new Suit("spades");
}

    要想类型安全模式可以被扩展,只要提供一个受保护的构造函数就可以。然后其他人就可以扩展这个类,并且在子类中增加新的常量。类型安全枚举模式可扩展变形与可序列化变形是兼容的,但是两者组合的时候要非常小心,每个子类必须分配自己的序数,并且提供自己的readResolve方法。下边的Operation类是可扩展的、可序列化的类型安全枚举类(每个,枚举类型极其定义的枚举变量在JVMh中都是唯一的):

public abstract class Operation1 implements Serializable{
    private final transient String name;//标记为transient的变量不会被序列化
    
    protected Operation1(String name) {
        this.name = name;
    }
    public String toString(){
        return this.name;
    }
    public static Operation1 PLUS = new Operation1("+"){
        protected double eval(double x, double y) {
            return x + y;
        }
    };
    public static Operation1 MINUS = new Operation1("-"){
        protected double eval(double x, double y) {
            return x - y;
        }
    };
    public static Operation1 TIMES = new Operation1("*"){
        protected double eval(double x, double y) {
            return x * y;
        }
    };
    public static Operation1 DIVIDE = new Operation1("/"){
        protected double eval(double x, double y) {
            return x / y;
        }
    };
    protected abstract double eval(double x, double y);
    private static int nextOrdinal = 0;
    private final int ordinal = nextOrdinal++;
    private static final Operation1[] VALUES ={PLUS,MINUS,TIMES,DIVIDE};
    Object readResolve() throws ObjectStreamException{
        return VALUES[ordinal];
    }

    下面是Operation的一个子类,它增加了对数和指数的操作。他本身也是可以扩展的。

public abstract class ExtendedOperation1 extends Operation1 {
    ExtendedOperation1(String name) {
        super(name);
    }
    public static Operation1 LOG = new Operation1("log"){
        protected  double eval(double x, double y) {
            return Math.log(y)/Math.log(x);
        }
    };
    public static Operation1 EXP = new Operation1("exp"){
        protected double eval(double x, double y) {
            return x + y;
        }
    };
    
    private static int nextOrdinal = 0;
    private final int ordinal = nextOrdinal++;
    private static final Operation1[]  VALUES = {LOG,EXP};
    
    Object readResolve() throws ObjectStreamException{
        return VALUES[ordinal];
    }

    有一种很好的方法来实现可伸缩性的枚举,以下是第30条中的Operation类型的扩展版本:

/**
 * 虽然枚举类型是不能扩展的 , 但是可以通过接口类表示API中的操作的接口类型 . 
 * 你可以定义一个功能完全不同的枚举类型来实现这个接口 .  
 */
public interface Operation {
    double apply(double x,double y);
}
public enum BasicOperation implements Operation{
    PLUS("+"){      
        public double apply(double x, double y) {           
            return x + y;
        }
    },
    MINUS("-"){ 
        public double apply(double x, double y) {           
            return x - y;
        }
    },
    TIMES("*"){
         public double apply(double x, double y){
             return x * y;
         }
    },
    DIVIDE("/"){
        public double apply(double x, double y) {
            return x / y;
        }
    }; 
    private final String symbol;
    BasicOperation(String symbol) {
        this.symbol = symbol;
    }   
}

    你可以定义另外一个枚举类型,它实现这个接口,并用这个新类型的实例代替基本类型。例如,假设你想要定义一个上述操作类型的扩展,由求幂和求余操作组成。你所要做的就是编写一个枚举类型,让它实现Operation接口:

public enum ExtendedOperation implements Operation {
    
    EXP("^"){
        @Override
        public double apply(double x, double y) {
            return Math.pow(x , y);
        }
    },
    REMAINDER("%"){
        @Override
        public double apply(double x, double y) {
            return x % y;
        }
        
    };
    
     private final String symbol;
     ExtendedOperation(String symbol) {
            this.symbol = symbol;
     }   
}

    注意,在枚举中,不必像在不可扩展的枚举中所做的那样,利用特定于实例的方法实现来声明抽象的apply方法。这是因为抽象的方法是接口的一部分。

    不仅可以在任何需要“基本枚举”的地方单独传递一个"扩展枚举"的实例,而且除了那些基本类型的元素之外,还可以传递完整的扩展枚举类型,并使用它的元素。通过下面这个测试程序,体验一下上面定义过的所有扩展过的操作:

public class test1 {
    public static void main(String[] args) {
        double x = 2;
        double y = 4;
        test(ExtendedOperation.class, x, y);
    }
    private static <T extends Enum<T> & Operation> void test(Class<T> opSet,
            double x, double y) {
        for (Operation op : opSet.getEnumConstants())
            System.out.printf("%f %s %f= %f%n", x, op, y, op.apply(x, y));
    }
}

    第二种方法是使用Collection<? extends Operation>,这个有限的通配符类型,作为opSet参数的类型:

import java.util.Arrays;
import java.util.Collection;
public class test2 {
public static void main(String[] args) {
    double x = 2;
    double y = 4 ;
    
    test(Arrays.asList(ExtendedOperation.values()),x,y);
}
private static void test(Collection<? extends Operation> opSet,double x, double y) {
    for (Operation op : opSet) {
        System.out.printf("%f %s %f= %f%n", x, op, y, op.apply(x, y));
    }
}
}

    上面这两段程序用命令行参数 2和4运行时,都会产生这样的输出:
2.000000 ^ 4.000000= 16.000000
2.000000 % 4.000000= 2.000000

    用接口模拟可伸缩枚举有个小小的不足,即无法将实现从一个枚举类型继承到另一个枚举类型。在上Operation的实例中,保存和获取与某项操作相关联的符号的逻辑代码,可以复制到BasicOperation和ExtendedOperation中。在这个例子中是可以的,因为复制的代码非常少。如果共享功能比较多,则可以将它封装在一个辅助类或者静态辅助方法中,来避免代码的复制工作。

    总而言之,虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。这样允许客户端编写自己的枚举来实现接口。如果API是根据接口编写的,那么在可以使用基础枚举类型的任何地方,也就可以使用这些枚举。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容

  • 枚举类型是指由一组固定的常量组成合法值的类型,例如一年中的季节,太阳系中的行星或者一副牌中的花色。在编程语言...
    小小辉_710a阅读 1,438评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,663评论 18 139
  • Java 1.5发行版本新增了两个引用类型家族:枚举类型(Enumerate类)和注解类型(Annotation接...
    Timorous阅读 410评论 0 0
  • { "Unterminated string literal.": "未终止的字符串文本。", "Identifi...
    一粒沙随风飘摇阅读 10,573评论 0 3
  • 昨天晚上我们学习完了第一幕,复习完昨天学的知识:丹麦的一个老国王死了,本该是哈姆莱特来继承王位,结果却是他的叔父来...
    petermeng阅读 346评论 3 5