Java 枚举详解

为何要用枚举

在博客系统中,一篇文章有且可能有这几种状态, 数据库中article文章表中state字段存储数值,表示其状态:java

  • 0(已发表Published)
  • 1(草稿Draft)
  • 2(撤回撤回(Delete)

文章实体类中用整数类型的state实例变量标识状态:程序员

public class Article {
    /* 文章状态可能值:0,1,2 */
    private int state;
    ...
}

Service层调用DAO层修改文章状态为‘已发表’:数据库

/**
 * dao接口修改文章状态方法
 * @param articleId 文章ID
 * @param state 状态
 */
int updateState(int articleId, int state);


// `Service`层修改文章状态的调用Dao代码:
articleDao.updateState(id, 0);

以上代码有两个问题:编程

  1. state参数传递并无限定范围(0,1,2);
  2. 传递数据参数的代码,缺乏语义,不看文档或注释不知道0是什么含义;

先来解决第二个问题, 在JDK1.5前经常使用的解决方式:mybatis

/**
 * 定义了文章的状态
 */
public interface ArticleState{
    // 发布状态
    int PUBLISHED = 0;
    // 草稿状态
    int DRAFT = 1;
    // 撤回状态
    int DELETE = 2;
}

此时修改文章状态的代码:函数

articleDao.updateState(id, ArticleState.PUBLISHED);

然而此处没有限制必须经过ArticleState传递参数,JDK1.5后提供了枚举来解决这类问题。工具

Java中声明

在java中,使用enum关键字声明枚举类测试

/**
 * 文章状态枚举类
 */
public enum ArticleStateEnum{
    PUBLISHED,
    DRAFT,
    DELETE;
}

而后修改DAO接口:this

/**
 * dao接口修改文章状态方法
 * @param articleId 文章ID
 * @param state 状态
 */
int updateState(int id, ArticleStateEnum state);

接着Service调用:code

// 修改文章状态为发表
articleDao.updateState(id, ArticleStateEnum.PUBLISHED);

以上代码语义清晰,如今传递参数的类型为ArticleStateEnum, 解决了以前描述的两个问题

枚举的本质

使用JDK附带工具javap反编译生枚举类字节码, 注javap反编译只会获得public成员:

枚举类本质

看反编译的获得的代码:

  1. class声明,意味着枚举的本质也是类;
  2. 父类声明为java.lang.Enum<>, 意味着枚举类不容许显式使用extends声明父类,包括声明为java.lang.Enum<>也会报错;
  3. 枚举常量,经过public static final修饰符实现,ArticlestateEnum类型声明,意味着全部枚举常量本质是当前枚举类的对象;
  4. values()方法valueOf(String)方法;

这些转换工做是javac编译器帮咱们实现的,JVM并不知道枚举的存在,javac帮咱们作了一些语法上的转化、简化程序员编程,这种方式称为语法糖。

枚举类VS普通类

枚举类就是类,按照这个逻辑来测试下它和普通类的差异

添加构造函数:

添加构造函数

红色行提示编译错误“找不到这样的构造函数”,常量声明处添加参数,以下代码正确:

public enum ArticleStateEnum{
    PUBLISHED(0, "已发布"),
    DRAFT(1, "草稿"),
    DELETE(2, "撤销");

    /** 表明的数值 */
    private int value;
    /** 信息提示 */
    private String message;

    ArticleStateEnum(int value, String message) {
        this.value = value;
        this.message = message;
    }

    // get方法
}

能够推测到常量声明的地方,等价于调用构造函数,一般咱们都会为字段添加GET方法不添加SET方法,保证枚举常量的不变性。

枚举类VS普通类的不一样点:

  1. 不能够显示声明继承关系;
  2. 常量声明,等价调用构造方法;
  3. 容许有多个构造方法,但修饰符有且仅是private;
  4. 其余地方同类通常无二,能够添加自定添加(方法、字段,抽象成员), 实现接口;

枚举类VS匿名类

看看如下如此夸张的写法,也能编译成功:

枚举类的匿名类写法

观察生成的字节码文件:

字节码文件

把枚举常量声明的地方替换成构造方法调用new ArticleStateEnum(v1, m1),这不就是匿名类的声明吗!

如今向枚举类内添加抽象方放,看看结果:

抽象方法的枚举

编译报错“提示有抽象方法未实现”,验证了前面的猜测,这是匿名类的实现,不过不能够显式的使用使用匿名实现枚举类的方式!

经常使用方法

详细参见API文档Enum类:

public enum ArticleStateEnum{
    PUBLISHED,
    DRAFT,
    DELETE;

    public static void main(String[] args) {
        ArticleStateEnum[] states =  ArticleStateEnum.values(); // 1. 得到全部枚举常量
        for(ArticleStateEnum state: states) {
            System.out.println("序号:" + state.ordinal() + " 名字:" + state); //2. 输出声明序号和名称
        }

        System.out.println("......................................");
        ArticleStateEnum draft = ArticleStateEnum.valueOf("DRAFT"); //3. 得到某个枚举常量,依据字符串
        if(ArticleStateEnum.DRAFT == draft) {
            System.out.println(ArticleStateEnum.valueOf("DRAFT").name()); //4. name方法输出名字
        }
    }
}

输出...

序号:0 名字:PUBLISHED
序号:1 名字:DRAFT
序号:0 名字:DELETE

DRAFT

JAVA中枚举的缺点

java中枚举给咱们带来强大的语义的时候,又因为枚举常量对象的本质,给咱们带了来庞大性,不如C语言的枚举的轻量:

  1. 枚举常量不能够像C语言同样使用移位运算。
  2. 枚举常量和外部交互麻烦,好比:
    • 在mybatis中保存带有枚举字段的实体时,须要你编写转化器(除非按照默认的声明顺序);
    • 转化为JSON数据时;
    • Spring MVC对请求参数封装时,须要自定义转换器;
  3. 枚举常量没法继承,意味着类似的枚举类之间没法继承,致使产生冗余代码;

建议无特殊状况仍是使用枚举常量,毕竟软件的正确性是最重要的

相关文章
相关标签/搜索