Guide to EnumSet – EnumSet指南

最后修改: 2018年 10月 12日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll explore the EnumSet collection from the java.util package and discuss its peculiarities.

在本教程中,我们将探讨来自java.util包的EnumSet集合,并讨论其特殊性。

We’ll first show the main features of the collection and after that, we’ll go through the internals of the class in order to understand its benefits.

我们将首先展示该集合的主要特征,之后,我们将了解该类的内部情况,以了解其优点。

Finally, we’ll cover the main operations that it provides and implement some basic examples.

最后,我们将介绍它所提供的主要操作并实现一些基本的例子。

2. What Is an EnumSet

2.什么是EnumSet

An EnumSet is a specialized Set collection to work with enum classes. It implements the Set interface and extends from AbstractSet:

一个EnumSet是一个专门的Set集合,用于处理enum。它实现了Set接口,并从AbstractSet扩展而来。

EnumSet-1-2

Even though AbstractSet and AbstractCollection provide implementations for almost all the methods of the Set and Collection interfaces, EnumSet overrides most of them.

尽管AbstractSetAbstractCollectionSetCollection接口的几乎所有方法提供了实现,但EnumSet覆盖了其中的大多数。

When we plan to use an EnumSet we have to take into consideration some important points:

当我们计划使用一个EnumSet时,我们必须考虑到一些重要的问题。

  • It can contain only enum values and all the values have to belong to the same enum
  • It doesn’t allow to add null values, throwing a NullPointerException in an attempt to do so
  • It’s not thread-safe, so we need to synchronize it externally if required
  • The elements are stored following the order in which they are declared in the enum
  • It uses a fail-safe iterator that works on a copy, so it won’t throw a ConcurrentModificationException if the collection is modified when iterating over it

3. Why Use EnumSet

3.为什么使用EnumSet

As a rule of thumb, EnumSet should always be preferred over any other Set implementation when we are storing enum values.

根据经验,当我们存储enum值时,EnumSet应始终优先于任何其他Set实现。

In the next sections, we’ll see what makes this collection better than others. In order to do so, we’ll briefly show the internals of the class to get a better understanding.

在接下来的章节中,我们将看到是什么让这个集合比其他集合更好。为了做到这一点,我们将简要地展示该类的内部结构,以获得更好的理解。

3.1. Implementation Details

3.1.实施细节

EnumSet is a public abstract class that contains multiple static factory methods that allow us to create instances. The JDK provides 2 different implementations – are package-private and backed by a bit vector:

EnumSet是一个public abstract类,包含多个静态工厂方法,允许我们创建实例。JDK提供了2种不同的实现–都是包-private,并由一个比特向量支持。

  • RegularEnumSet and
  • JumboEnumSet

RegularEnumSet uses a single long to represent the bit vector. Each bit of the long element represents a value of the enum. The i-th value of the enum will be stored in the i-th bit, so it’s quite easy to know whether a value is present or not. Since long is a 64-bit data type, this implementation can store up to 64 elements.

RegularEnumSet使用单个long来表示位向量。long元素的每一个位代表enum的一个值。枚举的第i个值将被存储在第i个位上,所以要知道一个值是否存在是很容易的。由于long是一个64位的数据类型,这个实现最多可以存储64个元素。

On the other hand, JumboEnumSet uses an array of long elements as a bit vector. This lets this implementation store more than 64 elements. It works pretty much like the RegularEnumSet but making some extra calculations to find the array index where the value is stored.

另一方面,JumboEnumSet使用一个long元素的数组作为位向量。这让这个实现可以存储超过64个元素。它的工作原理与RegularEnumSet差不多,但要进行一些额外的计算来找到存储值的数组索引。

Unsurprisingly, the first long element of the array will store the 64 first values of the enum, the second element the next 64, and so on.

不出所料,数组的第一个长元素将存储enum的64个初值,第二个元素是下一个64,以此类推。

EnumSet factory methods create instances of one implementation or another depending on the number of elements of the enum:

EnumSet工厂方法根据enum的元素数量创建一个或另一个实现的实例。

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

Keep in mind that it only takes into account the size of the enum class, not the number of elements that will be stored in the collection.

请记住,它只考虑了enum类的大小,而不是将被存储在集合中的元素数量。

3.2. Benefits from Using an EnumSet

3.2.使用EnumSet的好处

Due to the implementation of an EnumSet that we’ve described above, all the methods in an EnumSet are implemented using arithmetic bitwise operations. These computations are very fast and therefore all the basic operations are executed in a constant time.

由于我们上面描述的EnumSet的实现,EnumSet中的所有方法都是使用算术位操作实现的。这些计算非常快,因此所有的基本操作都是在恒定时间内执行的。

If we compare EnumSet with other Set implementations like HashSet, the first is usually faster because the values are stored in a predictable order and only one bit needs to be examined for each computation. Unlike HashSet, there’s no need to compute the hashcode to find the right bucket.

如果我们将EnumSet与其他Set实现(如HashSet)进行比较,前者通常更快,因为值是以可预测的顺序存储的,每次计算只需要检查一个比特。与HashSet不同,不需要计算hashcode来找到正确的桶。

Moreover, due to the nature of bit vectors, an EnumSet is very compact and efficient. Therefore, it uses less memory, with all the benefits that it brings.

此外,由于位向量的性质,EnumSet是非常紧凑和高效的。因此,它使用较少的内存,并带来所有的好处。

4. Main Operations

4.主要业务

The majority of the methods of an EnumSet work like any other Set, with the exception of the methods to create instances.

EnumSet的大多数方法与其他Set一样工作,除了创建实例的方法之外。

In the next sections, we’ll show in detail all the creational methods and we’ll cover briefly the rest of the methods.

在接下来的章节中,我们将详细展示所有的创建方法,并将简要介绍其余的方法。

In our examples, we’ll work with a Color enum:

在我们的例子中,我们将使用一个Color enum

public enum Color {
    RED, YELLOW, GREEN, BLUE, BLACK, WHITE
}

4.1. Creational Methods

4.1.创造性的方法

The most simple methods to create an EnumSet are allOf() and noneOf(). This way we can easily create an EnumSet containing all the elements of our Color enum:

创建EnumSet的最简单的方法是allOf()noneOf()这样,我们可以很容易地创建一个EnumSet,包含我们Color枚举的所有元素。

EnumSet.allOf(Color.class);

Likewise, we can use noneOf() to do the opposite and create an empty collection of Color:

同样地,我们可以使用noneOf()来做相反的事情,创建一个Color的空集合。

EnumSet.noneOf(Color.class);

If we want to create an EnumSet with a subset of the enum elements we can use the overloaded of() methods. It’s important to differentiate between the methods with a fixed number of parameters up to 5 different ones and the one that uses varargs:

如果我们想用enum元素的一个子集创建一个EnumSet,我们可以使用重载的of()方法。重要的是要区分有固定数量参数的方法和使用varargs的方法,前者最多有5个不同参数。

of-1

The Javadoc states that the performance of the varargs version can be slower than the others because of the creation of the array. Therefore, we should use it only if we initially need to add more than 5 elements.

Javadoc指出,由于数组的创建,varargs版本的性能会比其他版本慢。因此,我们应该只在最初需要添加超过5个元素的时候使用它。

Another way to create a subset of an enum is by using the range() method:

创建enum的子集的另一种方法是使用range()方法:

EnumSet.range(Color.YELLOW, Color.BLUE);

In the example above, the EnumSet contains all the elements from Yellow to Blue. They follow the order defined in the enum:

在上面的例子中,EnumSet包含了从YellowBlue.的所有元素,它们遵循enum中定义的顺序。

[YELLOW, GREEN, BLUE]

Notice that it includes both the first and last elements specified.

注意,它包括指定的第一个和最后一个元素。

Another useful factory method is the complementOf() that allows us to exclude the elements passed as parameters. Let’s create an EnumSet with all the Color elements except black and white:

另一个有用的工厂方法是complementOf(),它允许我们排除作为参数传递的元素。让我们创建一个EnumSet,其中包含所有Color元素,除了黑色和白色。

EnumSet.complementOf(EnumSet.of(Color.BLACK, Color.WHITE));

If we print this collection we can see that it contains all the other elements:

如果我们打印这个集合,我们可以看到它包含所有其他的元素。

[RED, YELLOW, GREEN, BLUE]

Finally, we can create an EnumSet by copying all the elements from another EnumSet:

最后,我们可以通过复制另一个EnumSet中的所有元素来创建一个EnumSet

EnumSet.copyOf(EnumSet.of(Color.BLACK, Color.WHITE));

Internally, it calls the clone method.

在内部,它调用clone方法。

Moreover, we can also copy all the elements from any Collection that contains enum elements. Let’s use it to copy all the elements of a list:

此外,我们还可以从任何包含enum元素的Collection中复制所有元素。让我们用它来复制一个列表中的所有元素。

List<Color> colorsList = new ArrayList<>();
colorsList.add(Color.RED);
EnumSet<Color> listCopy = EnumSet.copyOf(colorsList);

In this case, the listCopy only contains the red color.

在这种情况下,listCopy只包含红色。

4.2. Other Operations

4.2.其他业务

The rest of the operations work in the exact same way as any other Set implementation and there is no difference in how to use them.

其余的操作与其他Set实现的工作方式完全相同,在如何使用它们方面没有区别。

Therefore, we can easily create an empty EnumSet and add some elements:

因此,我们可以很容易地创建一个空的EnumSet并添加一些元素。

EnumSet<Color> set = EnumSet.noneOf(Color.class);
set.add(Color.RED);
set.add(Color.YELLOW)

Check if the collection contains a specific element:

检查该集合是否包含一个特定的元素。

set.contains(Color.RED);

Iterate over the elements:

对元素进行迭代。

set.forEach(System.out::println);

Or simply remove elements:

或者干脆删除元素。

set.remove(Color.RED);

This, of course, among all the other operations that a Set supports.

当然,这是在Set支持的所有其他操作中。

5. Conclusion

5.结论

In this article, we’ve shown the main features of EnumSet, its internal implementation and how we can benefit from using it.

在这篇文章中,我们展示了EnumSet的主要特征,它的内部实现以及我们如何从使用它中获益。

We’ve also covered the main methods that it offers and implemented some examples to show how we can use them.

我们还介绍了它所提供的主要方法,并实施了一些例子来说明我们如何使用它们。

As always, the full source code of the examples is available over on GitHub.

一如既往,这些示例的完整源代码可在GitHub上获得