EnumSet指南

2023/06/07

1. 概述

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

我们将首先展示该集合的主要特性,然后,我们将介绍该类的内部结构以了解它的好处。

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

2. 什么是EnumSet

EnumSet是一个专门的Set集合,用于处理枚举类。它实现了Set接口并从AbstractSet扩展:

尽管AbstractSet和AbstractCollection为Set和Collection接口的几乎所有方法提供了实现,但EnumSet覆盖了其中的大部分方法。

当我们计划使用EnumSet时,我们必须考虑一些要点:

  • 它只能包含枚举值,并且所有值都必须属于同一个枚举
  • 它不允许添加空值,尝试这样做会抛出NullPointerException
  • 它不是线程安全的,所以如果需要我们需要在外部同步它
  • 元素按照它们在枚举中声明的顺序存储
  • 它使用在副本上工作的故障安全迭代器,因此如果在迭代集合时修改集合,它不会抛出ConcurrentModificationException

3. 为什么使用EnumSet

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

在接下来的部分中,我们将看到是什么使该集合比其他集合更好。为此,我们将简要展示该类的内部结构以便更好地理解。

3.1 实现细节

EnumSet是一个公共抽象类,包含多个允许我们创建实例的静态工厂方法。JDK提供了2种不同的实现-包私有并由位向量支持:

  • RegularEnumSet
  • JumboEnumSet

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

另一方面,JumboEnumSet使用long元素数组作为位向量,这使此实现可以存储超过64个元素。它的工作方式与RegularEnumSet非常相似,但会进行一些额外的计算以找到存储值的数组索引。

不出所料,数组的第一个long元素将存储枚举的前64个值,第二个元素将存储接下来的64个值,依此类推。

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

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

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

3.2 使用EnumSet的好处

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

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

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

4. 主要业务

EnumSet的大多数方法的工作方式与任何其他Set类似,但创建实例的方法除外。

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

在我们的示例中,我们将使用Color枚举:

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

4.1 创建方法

创建EnumSet的最简单方法是allOf()和noneOf(),通过这种方式,我们可以轻松创建一个包含Color枚举的所有元素的EnumSet:

EnumSet.allOf(Color.class);

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

EnumSet.noneOf(Color.class);

如果我们想用枚举元素的子集创建一个EnumSet,我们可以使用重载的of()方法。区分具有固定参数数量(最多5个不同参数)和使用可变参数的方法非常重要:

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

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

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

在上面的示例中,EnumSet包含从YELLOW到BLUE的所有元素。它们遵循枚举中定义的顺序:

[YELLOW, GREEN, BLUE]

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

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

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

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

[RED, YELLOW, GREEN, BLUE]

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

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

在内部,它调用clone方法。

此外,我们还可以从任何包含枚举元素的集合中复制所有元素。让我们用它来复制列表的所有元素

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

在这种情况下,listCopy仅包含RED。

4.2 其他操作

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

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

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

检查集合是否包含特定元素:

set.contains(Color.RED);

遍历元素:

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

或者简单地删除元素:

set.remove(Color.RED);

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

5. 总结

在本文中,我们展示了EnumSet的主要特性、其内部实现以及我们如何从中受益。

我们还介绍了它提供的主要方法,并实现了一些示例来展示我们如何使用它们。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章