java中的枚举类型——Enum

日常java编程中,常见用一些静态常量表示一些状态码,特殊含义的标志等,例如:

1
2
3
4
5
public class A {
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAIL = 2;
public static final int STATUS_CANCEL = 3;
}

这样使用,虽然看上去很好,但是有不少隐形的问题:

  • 类型不安全;
  • 必须确保int范围;
  • 如果打印的话,只能看到int的值,查看含义的话,需要人工比对

其实这种情况,java的Enum往往是更好的选择。
现在结合Android开源图片下载项目Android-Universal-Image-Loader,我们来简略分析一下Enum的用法。
Enum的基本使用如下:

1
2
3
public Enum Scheme {
HTTP, HTTPS, FILE, CONTENT, ASSETS, DRAWABLE, UNKNOWN;
}

使用Enum后,立竿见影的好处是使用switch很方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public InputStream getStream(String imageUri, Object extra) throws IOException {
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}
}

而且这个时候,如果把变量的值打印出来的话,会打印Enum的内容,而不是int整型,非常直观:

1
2
3
4
5
6
Scheme scheme = Scheme.HTTPS;
System.out.println(s);

/* 输出结果:
* HTTPS
*/

但实际上,Enum的使用方式远非如此简单,Enum就是一个类,可以包含变量,可以包含函数,当然,也有自己的构造函数。
首先,Enum有两个静态方法:

1
2
3
4
values()
获取枚举类型的所有枚举常量
valueOf(Class<T> EnumType, String name)
返回带指定名称的指定枚举类型的枚举常量。

使用方法以最原始的Enum举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Enum Scheme {
HTTP, HTTPS, FILE, CONTENT, ASSETS, DRAWABLE, UNKNOWN;
}

for (Scheme scheme : Scheme.values()) {
System.out.print(scheme);
System.out.print(" ");
}
/* 输出结果:
* HTTP HTTPS FILE CONTENT ASSETS DRAWABLE UNKNOWN
*/


System.out.println(Scheme.valueOf(Scheme.class, "HTTP"));
/* 输出结果:
* HTTP
*/

这个时候,我们做一些小小的改动,去为Enum添加一个变量,同时重写toString方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Enum Scheme {
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"),
ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN(
"unknown");

private String scheme;

Scheme(String s) {
scheme = s;
}

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

这个时候,我们打印一下看看:

1
2
3
4
5
6
7
for (Scheme scheme : Scheme.values()) {
System.out.print(scheme);
System.out.print(" ");
}
/* 输出结果:
* http https file content assets drawable unknown
*/

可以看到,我们在Enum中的变量可以被Enum中的方法使用。我就说嘛,枚举就是一个类。
现在,让我们仔细看一下Android-Universal-Image-Loader中的Enum定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public Enum Scheme {
HTTP("http"), HTTPS("https"), FILE("file"), CONTENT("content"),
ASSETS("assets"), DRAWABLE("drawable"), UNKNOWN("");

private String scheme;
private String uriPrefix;

Scheme(String scheme) {
this.scheme = scheme;
uriPrefix = scheme + "://";
}

/**
* Defines scheme of incoming URI
*
* @param uri URI for scheme detection
* @return Scheme of incoming URI
*/

public static Scheme ofUri(String uri) {
if (uri != null) {
for (Scheme s : values()) {
if (s.belongsTo(uri)) {
return s;
}
}
}
return UNKNOWN;
}

private boolean belongsTo(String uri) {
return uri.toLowerCase(Locale.US).startsWith(uriPrefix);
}

/** Appends scheme to incoming path */
public String wrap(String path) {
return uriPrefix + path;
}

/** Removed scheme part ("scheme://") from incoming URI */
public String crop(String uri) {
if (!belongsTo(uri)) {
throw new IllegalArgumentException(String.format("URI [%1$s] doesn't have expected scheme [%2$s]", uri, scheme));
}
return uri.substring(uriPrefix.length());
}
}

我们看到,这里面针对协议的Enum,除了定义基本的Enum类型,还提供了根据url去判断是哪一种Enum的静态方法。正因为有这个静态方法,所以,在项目中有了如下优雅的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch (Scheme.ofUri(imageUri)) {
case HTTP:
case HTTPS:
return getStreamFromNetwork(imageUri, extra);
case FILE:
return getStreamFromFile(imageUri, extra);
case CONTENT:
return getStreamFromContent(imageUri, extra);
case ASSETS:
return getStreamFromAssets(imageUri, extra);
case DRAWABLE:
return getStreamFromDrawable(imageUri, extra);
case UNKNOWN:
default:
return getStreamFromOtherSource(imageUri, extra);
}

Enum那么厉害,那在我们的Android开发中要注意啥呢?要注意的就是,尽量不用Enum
为啥???
早期谷歌语焉不详的提出,enum使用在Android中会产生额外编译开销,后来又语焉不详的把在《性能建议》中不建议使用Enum删除了。个人理解,可能早期的编译器,对Enum的编译不友好,毕竟,Android是自己的一套编译器,而不是java那一套,后来不断改进不断改进,这个问题缓和了。但毕竟Android开发还要考虑早期用户,所以,这里,并不建议大量使用Enum在我们的项目中。
关于这点,可以参考:
Why doesn’t Android use more enums?
Why was “Avoid Enums Where You Only Need Ints” removed from Android’s performance tips?