Java中的EnumSet

文章作者:Tyan
博客:noahsnail.com

1. EnumSet

EnumSet是Java Set接口的一个特别实现,在JDK 1.5中开始支持,Enum类型也正式引入到了Java中。与其它保存枚举常量的Set相比,EnumSet具有更好的性能,同时其也是Java中的优秀特性之一。下面从三个方面来介绍EnumSet,what,how,when。

2. What is EnumSet

EnumSetSet接口的一个实现,它只能用来存储Enum常量或其子类,不能存储其它类型。EnumSet是设计模式中工厂方法创建实例的一个很好例子。

EnumSet被声明为abstract class类型,EnumSet有两种实现方式,RegularEnumSetJumboEnumSet,但是这两种实现方式是包私有的,不能在包外访问,因此必须使用工厂方法来创建并返回EnumSet实例,不能通过构造函数来创建。EnumSet中提供了多种创建EnumSet实例的静态工厂方法,例如of方法(进行了函数重载),copyOf方法,noneOf方法等。

3. How EnumSet is implemented in Java

上面已经说了,EnumSet是一个抽象类,有两个具体实现:java.util.RegularEnumSetjava.util.JumboEnumSet。二者的主要区别在于前者使用long来表示元素的数量,而后者使用long[]来表示元素的数量。这二者中表示元素数量使用的是位域结构,即通过long的二进制位数来表示元素数量,例如:RegularEnumSet使用的是long表示元素数量,long数值是通过64位二进制表示的,因此其只能包含的元素最大数量为64,如果元素数目大于64,采用的是JumboEnumSet表示,JumboEnumSetlong[]中有一个long数值,就能表示64个元素,两个long数值就能表示128个元素,以此类推。

4. When to use EnumSet in Java

《Effective Java》中的Item 32讲述了一个EnumSet的使用场景,推荐去看一下。当你需要对枚举类型进行特定的分组时,你可以使用EnumSet。例如,一周中有七天,你想将周末单独分出来的时候(enum int pattern):

1
2
3
4
5
private enum DayOfWeek{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}

private EnumSet weekend = EnumSet.of(SATURDAY,SUNDAY);

5. Important points about EnumSet

  • 一个EnumSet中只能包含一种枚举类型。

  • EnumSet中不能放入null元素,放入会抛出空指针异常。

  • EnumSet是线程非安全的。

  • EnumSet的Iterator是自动防故障和弱一致的,不会抛出并发修改异常,即在迭代过程的中的修改结果不一定会在迭代过程中显示。

  • EnumSet是高性能的Java集合。由于是基于数组的访问,因此addcontainsnext方法的时间复杂度为O(1)。

  • 存储枚举常量时使用EnumSet而不要用HashSet

6. Source Code of EnumSet

EnumSet:

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
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable {
...
EnumSet(Class<E>elementType, Enum<?>[] universe) {
this.elementType = elementType;
this.universe = universe;
}

/**
* Creates an empty enum set with the specified element type.
*
* @param <E> The class of the elements in the set
* @param elementType the class object of the element type for this enum
* set
* @return An empty enum set of the specified type.
* @throws NullPointerException if <tt>elementType</tt> is null
*/
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");

if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
...
}

RegularEnumSet:

1
2
3
4
5
6
7
8
9
10
11
12
13
class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
...
/**
* Bit vector representation of this set. The 2^k bit indicates the
* presence of universe[k] in this set.
*/
private long elements = 0L;

RegularEnumSet(Class<E>elementType, Enum<?>[] universe) {
super(elementType, universe);
}
...
}

JumboEnumSet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
private static final long serialVersionUID = 334349849919042784L;

/**
* Bit vector representation of this set. The ith bit of the jth
* element of this array represents the presence of universe[64*j +i]
* in this set.
*/
private long elements[];

// Redundant - maintained for performance
private int size = 0;

JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {
super(elementType, universe);
elements = new long[(universe.length + 63) >>> 6];
}
...
}

7. Example of EnumSet

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.EnumSet;
import java.util.Set;

public class EnumSetDemo {

private enum Color {
RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255);
private int r;
private int g;
private int b;
private Color(int r, int g, int b) {
this.r = r;
this.g = g;
this.b = b;
}
public int getR() {
return r;
}
public int getG() {
return g;
}
public int getB() {
return b;
}
}


public static void main(String args[]) {
// this will draw line in yellow color
EnumSet<Color> yellow = EnumSet.of(Color.RED, Color.GREEN);
drawLine(yellow);
// RED + GREEN + BLUE = WHITE
EnumSet<Color> white = EnumSet.of(Color.RED, Color.GREEN, Color.BLUE);
drawLine(white);
// RED + BLUE = PINK
EnumSet<Color> pink = EnumSet.of(Color.RED, Color.BLUE);
drawLine(pink);
}


public static void drawLine(Set<Color> colors) {
System.out.println("Requested Colors to draw lines : " + colors);
for (Color c : colors) {
System.out.println("drawing line in color : " + c);
}
}
}

Output:
Requested Colors to draw lines : [RED, GREEN]
drawing line in color : RED
drawing line in color : GREEN

Requested Colors to draw lines : [RED, GREEN, BLUE]
drawing line in color : RED
drawing line in color : GREEN
drawing line in color : BLUE

Requested Colors to draw lines : [RED, BLUE]
drawing line in color : RED
drawing line in color : BLUE

参考资料:

1、https://jaxenter.com/enumset-in-java-regularenumset-vs-jumboenumset-106051.html

2、Effective Java 2.0版本中的Item 1,本文就是在看《Effective Java》时看到的EnumSet,才想要仔细研究一下EnumSet的。Item 32讲述了EnumSet的使用场景。

3、http://brokendreams.iteye.com/blog/2267485

4、http://javarevisited.blogspot.kr/2014/03/how-to-use-enumset-in-java-with-example.html

如果有收获,可以请我喝杯咖啡!