优雅编程之这样使用枚举和注解,你就“正常”了(二十九)

开心一笑

提出问题

项目中如何使用枚举和注解???

解决问题

用enum替换int常量

例如:下面是公司项目的一个标准的enum实例。

package com.evada.de.common.enums;

/**
 * 状态枚举
 * @author Ay
 */
public enum StatusEnum {
    /** 0:已删除 */
    DELETED("0","已删除"),
    /** 1:启用 */
    ENABLE("1","启用"),
    /** 2:禁用 */
    DISABLE("2","禁用");

    /** 状态值 */
    private final String code;

    private final String name;

    private StatusEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }

    @Override
    public String toString() {
        return code;
    }

    public String getName() {
        return name;
    }
}

注意,与枚举常量关联的行为,最好被实现成私有的或者包级私有的方法。

例如:

package com.evada.de;

/**
 * Created by Ay on 2016/10/2.
 */
public enum Operation {

    PLUS,MINUS,TIMES,DIVIDE;

    double apply(double x,double y){
        switch (this){
            case PLUS:return x + y;
            case MINUS:return x - y;
            case TIMES:return x * y;
            case DIVIDE:return x / y;
        }
        throw new AssertionError("Unknow op: " + this);
    }
}

上面代码是很脆弱的,如果添加一种类型,却忘记给switch添加相应的条件,枚举仍然可以编译,但是当你试图运用新的运算时,就会运行失败。

修改过后的实例:

package com.evada.de;

/**
 * Created by Ay on 2016/10/2.
 */
public enum Operation {

    PLUS("+"){
        @Override
        double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-"){
        @Override
        double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*"){
        @Override
        double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/"){
        @Override
        double apply(double x, double y) {
            return x / y;
        }
    };

    private final String symbol;
    Operation(String symbol){this.symbol = symbol;}

    @Override
    public String toString() { return this.symbol;}
    abstract double apply(double x,double y);
}

下面看一个嵌套枚举实例:个人感觉这段代码很优雅,贴出来相互学习下:

如果多个枚举常量同时共享相同的行为,则考虑策略枚举(嵌套枚举)。

/**
 * Created by Ay on 2016/10/2.
 */
public enum PayrollDay {

    MonDAY(PayType.WEEKDAY),
    WEEKDAY(PayType.WEEKDAY),
    TUESDAY(PayType.WEEKDAY),
    WENDESDAY(PayType.WEEKDAY),
    THURSDAY(PayType.WEEKDAY),
    FRIDAY(PayType.WEEKDAY),
    SATURDAY(PayType.WEEKEND),
    SUNDAY(PayType.WEEKEND);

    private PayType payType;

    PayrollDay(PayType payType){ this.payType = payType;}

    double pay(double hoursWorked,double payRate){
        return payType.pay(hoursWorked,payRate);
    }
    //这里是嵌套枚举
    private enum PayType{

        WEEKDAY{
            @Override
            double overtimePay(double hrs, double payRate) {
                return 0;
            }
        },WEEKEND{
            @Override
            double overtimePay(double hrs, double payRate) {
                return 0;
            }
        };

        private static final int HOURS_PRE_SHIFT = 8;

        abstract double overtimePay(double hrs,double payRate);

        double pay(double hoursWorked,double payRate){
            double basePay = hoursWorked * payRate;
            return basePay + overtimePay(hoursWorked,payRate);
        }
    }
}

枚举有个小小的性能缺点,即装载和初始化枚举时会有空间和时间成本。

总而言之,与int常量相比,枚举类型的优势是不可言喻的。枚举要易读得多,也更加安全,功能更加强大。

用实例域替换序数

例如:

enum Ensemble{
    SOLO,DUET,TRIO,QUARTET,QUINTET,SEXTET,SEPTEX,OCTEX,NONET,DECTET;
    public int numberOfMusicians(){
        return ordinal() + 1;//ordinal()用来返回枚举常量在类型中的数字位置。
    }
}

永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中:

正确做法是:

enum Ensemble{
    SOLO(1),DUET(2),TRIO(3),QUARTET(4),QUINTET(5),
    SEXTET(6),SEPTEX(7),OCTEX(8),NONET(9),DECTET(10);
    
    private final int numberOfMusicians;
    
    Ensemble(int size){
        this.numberOfMusicians = size;
    }
    
    public int numberOfMusicians(){
        return ordinal() + 1;
    }

}

Enum规范中写道:大多数程序员都不需要这个方法。除非你在编写的是这种数据结构,否则最好避免使用original方法。

用EnumSet代替位域

用OR位运算,将几个常量合并到一个集合中,称为位域。

class Text{
    public static final int STYLE_BOLE = 1 << 0;
    public static final int STYLE_ITALIC = 1 <<1;
    public static final int STYLE_UNDERLINE = 1 <<2;
    public static final int STYLE_STRIKETHROUGH = 1 <<3;
    public void applyStyles(int styles){ ... }
}

将前一个范例改为用枚举代替位域后的代码为:

class Text{

    public enum Style{BOLE,ITALIC,UNDERLINE,STRIKETHROUGH}
    public void applyStyles(Set<Style> styles){}
}

总而言之,正是因为枚举类型要用在集合中,所以没有理由用位域来表示它。

用EnumMap代替序数索引

说白了,就是类似于key和value形式,具体可以看《Effective Java》的具体实例。

总而言之,最好不要用序数来索引数组,而要使用EnumMap。如果你所表示的这种关系是多维的,就使用EnumMap<...,EnumMap<...>>。运用程序的程序员在一般情况下都不使用Enum.oridinal,即使要用也很少,因此这是一种特殊情况。

用接口模拟可伸缩的枚举

例如:

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 symbool;
    BasicOperation(String symbool){ this.symbool = symbool}

    @Override
    public String toString() {
        return symbool;
    }

    @Override
    public double apply(double x, double y) {
        return 0;
    }
}

上面例子有很好的扩展性,假设你想要定义一个上述操作类型的扩展,由求幂和求余操作组成,你需要做的就是编写一个枚举类型,让它实现Operation接口:

public interface Operation{
    double apply(double x,double y);
}

public enum BasicOperation implements Operation{

    PLUS("^"){
        public double apply(double x,double y){ return Math.pow(x,y);}
    },
    MINUS("%"){
        public double apply(double x,double y){ return x % y;}
    };

    private final String symbool;
    BasicOperation(String symbool){ this.symbool = symbool}

    @Override
    public String toString() {
        return symbool;
    }

    @Override
    public double apply(double x, double y) {
        return 0;
    }
}

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

注解优先于命名模式

命名模式:表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,Junit测试框架原本要求它的用户一定要用test作为测试方法名称的开头。

例如:

@Test
public void testLove(){
    System.out.println("I love you!!!");
}

总而言之,使用注解而不是命名模式

坚持使用Override注解

@Override只能用在方法声明中,它表示被注解的方法声明覆盖了超类型中的一个声明。如果坚持使用这个注解,可以防止一大类的非法错误。

在你想要覆盖超类声明的每个方法声明中使用Override注解。

总而言之,如果在你想要的每个方法声明中使用Override注解来覆盖超类声明,编译器就可以防止大量的错误,但有一个例外。在具体的类中,不必标注你确信覆盖了抽象方法声明的方法。

用标记接口定义类型

标记接口:是没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口。例如:Serializable.

标记接口胜过标记注解:标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型。

标记注解胜过标记接口:它可以通过默认的方式添加一个或者多个注解类型的元素,给已被使用的注解类型添加更多的信息。

那么该如何选择?

如果标记是运用到任何程序元素而不是类或者接口,就必须使用注解,因为只有类和接口可以用来实现或者扩展接口。如果标记只运用给类和接口,就要问问自己:我要编写一个还是多个只接受有这种标记的方法?如果是这种情况,就应该优先使用标记接口而非注解。这样你就可以用接口作为相关方法的参数类型,它真正可以为你提供编译时进行类型检查的好处。

读书感悟

来自亦舒《流金岁月》

  • 无论做什么,记得为自己而做,那就毫无怨言。
  • 那种难得的朋友。我成功,她不嫉妒。我委靡,她不轻视。人生得一知己足矣。
  • 快乐是要去寻找的,很少有天生幸福的人。
  • 你看得起你自己就好,管谁看不起你,肯帮固然好,不帮拉倒。
  • 气质,读书的唯一的用途是增加气质。世上确有气质这回事。

其他

如果有带给你一丝丝小快乐,就让快乐继续传递下去,欢迎转载,点赞,顶,欢迎留下宝贵的意见,多谢支持!

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

推荐阅读更多精彩内容

  • Java 1.5发行版本新增了两个引用类型家族:枚举类型(Enumerate类)和注解类型(Annotation接...
    Timorous阅读 404评论 0 0
  • 30、用enum代替int常量 枚举类型是指由一组固定的常量组成合法值的类型。在java没有引入枚举类型前,表示枚...
    Alent阅读 754评论 1 5
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,455评论 0 4
  • 不知不觉 阳光刺眼 夏天在大张旗鼓地宣誓主权 又是一个夏天 去年的这时候 每天上班 没有故事也不知道天空是不是蔚蓝...
    一树一花音乐阅读 239评论 0 0
  • 咏柳-贺知章 碧玉妆成一树高,万条垂下绿丝绦。 不知细叶谁裁出,二月春风似剪刀。 回乡偶书-贺知章 离别家乡岁月多...
    走在边缘阅读 167评论 0 0