资讯专栏INFORMATION COLUMN

简谈Java Enum

BicycleWarrior / 2906人阅读

摘要:常量接口是对接口的一种不良使用。如果这些常量最好被看作是枚举类型成员,那就应该用枚举类型来导出。因为客户端既不能创建枚举类型的实例,也不能对它进行扩展,因此很可能没有实例,而只有声明过的枚举常量。换句话说,枚举类型是实例受控的。

问题

我们偶尔能在项目中看到如下风格的代码:

public class ResponseCode {
    public static final int SUCCESS = 0;
    public static final int FAILURE = 10000;
    public static final int ILLEGAL_ARGUMENT = 10001;
    //........
}

这样的代码一般被叫做int枚举模式。其包含着大量缺点:

int枚举是编译时常量,被编译到客户端中,如果枚举常量关联的int发生变化,客户端必须重新编译,如果没有重新编译,程序仍可以运行,但行为就不确定了。

将int枚举常量翻译成可打印的字符串很麻烦。

在本例中,我要向客户端返回一个错误码和错误信息,错误码可以通过ResponseCode.FAILURE获得,但是错误信息恐怕只能以"FAILURE"这样的hard code返回了。

遍历一个组中所有的int枚举常量,获得int枚举组的大小,没有可靠的方法。

在这种int枚举模式的基础上,还有一种变体,我们称之为String枚举模式。虽然它提供了可打印的字符串,但这种方式存在性能问题,因为依赖于字符串的比较操作。

还有一种代码类似:

public interface CascadeConstant {
    String DELETION_CHECK_CODE = "deletion.check";
    String DELETION_DELETE_CODE = "deletion.delete";
    String DELETION_FORCE_DELETE_CODE = "deletion.forceDelete";
    String DELETION_CLEANUP_CODE = "deletion.cleanup";

    List DELETION_CODES = Arrays.asList(DELETION_CHECK_CODE, DELETION_DELETE_CODE, DELETION_FORCE_DELETE_CODE);
}

这样的代码我们一般称之为常量接口(constant interface)——这种接口不包含任何方法,它只包含静态的final域,每个域都导出一个常量。

类实现接口时,接口就充当可以引用这个类的实例类型。因此,类实现了接口,就表明客户端对这个类的实例可以实施某些动作。为了任何其他目的而定义的接口是不恰当的。

 常量接口是对接口的一种不良使用。类在内部使用某些常量,纯粹是实现细节,实现常量接口,会导致把这样的实现细节泄露到该类的导出API中,因为接口中所有的域都是及方法public的。类实现常量接口,这对于这个类的用户来讲并没有实际的价值。实际上,这样做返回会让他们感到更糊涂,这还代表了一种承诺:如果在将来的发行版本中,这个类被修改了,它不再需要使用这些常量了,依然必须实现这个接口,以确保二进制兼容性。如果非final类实现了常量接口,它的所有子类的命名空间都受到了污染。Java平台类库中存在几个常量接口,如java.io.ObjectStreamConstants,这些接口都是反面典型,不值得效仿。

那既然不适合存在全部都是导出常量的常量接口,那么如果需要导出常量,它们应该放在哪里呢?如果这些常量与某些现有的类或者接口紧密相关,就应该把这些常量添加到这个类或者接口中,注意,这里说添加到接口中并不是指的常量接口。在Java平台类库中所有的数值包装类都导出MIN_VALUE和MAX_VALUE常量。如果这些常量最好被看作是枚举类型成员,那就应该用枚举类型来导出。否则,应该使用不可实例化的工具类来导出这些常量。

实践

我们先看改良版的ResponseCode :

public enum ResponseCode {
    SUCCESS(0),
    ERROR(10000),
    ILLEGAL_ARGUMENT(10001);

    private final int code;

    ResponseCode(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }

    public static ResponseCode getEnum(int value) {
        for (ResponseCode responseCode : ResponseCode.values()) {
            if (responseCode.getCode()==value) {
                return responseCode;
            }
        }
        return null;
    }
}

这样就克服了我们之前提到的缺点:类型确定
在int枚举模式中或者String枚举模式中,我们会写出这样的方法签名:

//int 枚举模式
SendResponse(String description,int value)
//String枚举模式
//SendResponse(String description,int value)

这样的代码其实并不可靠,我们可以考虑:

SendResponse(String description,int code)

那么在调用的时候实质上是:

SendResponse(ResponseCode.ERROR.toString(),ResponseCode.ERROR.getCode())

为了更加方便,我们可以在此之上简单的封装一层:

AutoBuildAndSendResponse(ResponseCode responseCode){
  SendResponse(responseCode.toString(),responseCode.getCode();
}

这就很美滋滋。

在这个情况下,我们甚至还可以考虑在SendResponse方法中再加一个名为errorDetail的参数。利用方法重载,使Reponse的返回信息更为灵活。

不仅如此,Java的枚举类是很强大的。其本质是int值,并且背后基本原理也非常简单:它们就是通过共有的静态final域为每个枚举常量导出实例的类。因为没有可以访问的构造器,枚举类是真正的final。因为客户端既不能创建枚举类型的实例,也不能对它进行扩展,因此很可能没有实例,而只有声明过的枚举常量。换句话说,枚举类型是实例受控的。而且,Java的枚举类还可以添加任意的方法和域,并实现任意接口,而且也提供了所有的Object方法的高级实现

关于StringValue的较佳实践
public enum SourceDiskType {

    SYSTEM("system"),
    DATA("data"),;

    private String stringValue;

    SourceDiskType(String stringValue) {
        setStringValue(stringValue);
    }

    public String getStringValue() {
        return stringValue;
    }

    public void setStringValue(String stringValue) {
        this.stringValue = stringValue;
    }

    public static SourceDiskType getEnum(String stringValue) {
        if (null == stringValue) {
            return null;
        }

        for (SourceDiskType sourceDiskType : SourceDiskType.values()) {
            if (sourceDiskType.getStringValue().equals(stringValue)) {
                return sourceDiskType;
            }
        }
        return null;
    }
}

这是阿里云早期版本SDK中的一段代码。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70211.html

相关文章

  • 简谈Java String

    摘要:而用关键字调用构造器,总是会创建一个新的对象,无论内容是否相同。中对象的哈希码被频繁地使用比如在等容器中。字符串不变性保证了码的唯一性因此可以放心地进行缓存。对于所有包含方式新建对象包括的连接表达式,它所产生的新对象都不会被加入字符串池中。 前言 前阵子和同事在吃饭时聊起Java的String,觉得自己之前的笔记写的略显零散。故此又重新整理了一下。 String在Java中算是一个有意...

    ssshooter 评论0 收藏0
  • 简谈文件下载的三种方式

    摘要:一前言本文章将以报表下载为例,给大家介绍三种文件下载的方式。通过二进制数据流的方式下载这种方式是我目前采用的方式,用于处理报表下载。缺点对于数据量不大的文件,这种方式是可行的。 一、前言 本文章将以excel报表下载为例,给大家介绍三种文件下载的方式。 原文地址:简谈文件下载的三种方式 | Rychou 二、正文 1. 通过服务器文件地址下载 这是最常见的文件下载方式,大多数网站的音频...

    lsxiao 评论0 收藏0
  • 简谈文件下载的三种方式

    摘要:一前言本文章将以报表下载为例,给大家介绍三种文件下载的方式。通过二进制数据流的方式下载这种方式是我目前采用的方式,用于处理报表下载。缺点对于数据量不大的文件,这种方式是可行的。 一、前言 本文章将以excel报表下载为例,给大家介绍三种文件下载的方式。 原文地址:简谈文件下载的三种方式 | Rychou 二、正文 1. 通过服务器文件地址下载 这是最常见的文件下载方式,大多数网站的音频...

    2i18ns 评论0 收藏0
  • 简谈JavaScript闭包

    摘要:所以经常看到的说闭包就是绑定了上下文环境的函数。我更偏向于闭包是一个函数和声明该函数的词法环境的组合。里面的闭包先上一个闭包该例子的解释上面的代码,在函数里面定义的函数和这个函数声明的词法环境就形成了一个闭包。 闭包是什么 第一种说法:闭包创建一个词法作用域,这个作用域里面的变量被引用之后可以在这个词法作用域外面被自由访问,是一个函数和声明该函数的词法环境的组合 第二种说法:闭包就是...

    Zachary 评论0 收藏0

发表评论

0条评论

BicycleWarrior

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<