继承体系
EnumSet是一个abstract类,真正实现有两个,分别是RegularEnumSet与JumboEnumSet。这两个类的区别在于传入的枚举类型其内的实例个数,如果枚举实例个数小于64则使用RegularEnumSet否则使用JumboEnumSet。
产生实例方法
由于numSet是一个abstract类不能进行初始化操作,而两个子类并没有使用public修饰,意味着我们也不能直接初始化其子类。设计上还是需要EnumSet进行初始化。参考下面的源码,可以看到EnumSet提供了一系列static的方法帮助我们初始化实例。这里的初始化static方法可以分为两套,一个是使用枚举类型产生,一个是传递EnumSet拷贝出一份EnumSet进行使用。
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);
}
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
EnumSet<E> result = noneOf(elementType);
result.addAll();
return result;
}
public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) {
return s.clone();
}
public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c) {
if (c instanceof EnumSet) {
return ((EnumSet<E>)c).clone();
} else {
if (c.isEmpty())
throw new IllegalArgumentException("Collection is empty");
Iterator<E> i = c.iterator();
E first = i.next();
EnumSet<E> result = EnumSet.of(first);
while (i.hasNext())
result.add(i.next());
return result;
}
}
public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s) {
EnumSet<E> result = copyOf(s);
result.complement();
return result;
}
public static <E extends Enum<E>> EnumSet<E> of(E e) {
EnumSet<E> result = noneOf(e.getDeclaringClass());
result.add(e);
return result;
}
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
return result;
}
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) {
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
result.add(e3);
return result;
}
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) {
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
result.add(e3);
result.add(e4);
return result;
}
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4,
E e5)
{
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
result.add(e3);
result.add(e4);
result.add(e5);
return result;
}
@SafeVarargs
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
EnumSet<E> result = noneOf(first.getDeclaringClass());
result.add(first);
for (E e : rest)
result.add(e);
return result;
}
public static <E extends Enum<E>> EnumSet<E> range(E from, E to) {
if (from.compareTo(to) > 0)
throw new IllegalArgumentException(from + " > " + to);
EnumSet<E> result = noneOf(from.getDeclaringClass());
result.addRange(from, to);
return result;
}
RegularEnumSet源码解析
RegularEnumSet是用在枚举量小于64的场合的。除此之外set语义上仅关心是否存在目标元素,Enum又是较为固定的数据,因此在RegularEnumSet中仅使用一个long的字段,使用其上的位来表达enum是否存在。初始化为0,所有位上数据都是0,意味着没有任何元素。
private long elements = 0L;
添加元素方法
添加元素方法有4个,分别是,添加一个元素,添加范围元素,添加指定集合元素,添加默认全部:
public boolean add(E e) {
typeCheck(e);
long oldElements = elements;
elements |= (1L << ((Enum<?>)e).ordinal());
return elements != oldElements;
}
void addRange(E from, E to) {
elements = (-1L >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
}
public boolean addAll(Collection<? extends E> c) {
if (!(c instanceof RegularEnumSet))
return super.addAll(c);
RegularEnumSet<?> es = (RegularEnumSet<?>)c;
if (es.elementType != elementType) {
if (es.isEmpty())
return false;
else
throw new ClassCastException(
es.elementType + " != " + elementType);
}
long oldElements = elements;
elements |= es.elements;
return elements != oldElements;
}
void addAll() {
if (universe.length != 0)
elements = -1L >>> -universe.length;
}
这里可以看到都是使用位运算进行处理,接下来我们将最核心的位运算处理单独拉出来:
- 下面代码表示将1的数据往左移动ordinal个单位然后或上原本数据,最终原本数据的ordinal位置的数据将被赋值为1
elements |= (1L << ((Enum<?>)e).ordinal())
- 下面的代码是将原本数据中的from-to之间的位置为1,其中from.ordinal() - to.ordinal() - 1是一个负值会产生这样的效果:1111 1111 >>> -2 = 0000 0011,在这个基础上再进行左移就会产生from-to之间的位被置为1的效果
(-1L >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal();
- 下面这段代码是将枚举长度部分置为1其他为0的逻辑,与2一样,使用 -1 >>> 负值来控制
elements = -1L >>> -universe.length
删除元素方法
删除元素分为单个删除与批量删除:
public boolean remove(Object e) {
if (e == null)
return false;
Class<?> eClass = e.getClass();
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false;
long oldElements = elements;
// 1L进行左移,再取反,会出现64位除了目标位置都是1的效果
// 0与任何位 进行 &运算都是0,逻辑上就是针对该位置进行置0操作,达到删除的效果
elements &= ~(1L << ((Enum<?>)e).ordinal());
return elements != oldElements;
}
public boolean removeAll(Collection<?> c) {
if (!(c instanceof RegularEnumSet))
return super.removeAll(c);
RegularEnumSet<?> es = (RegularEnumSet<?>)c;
if (es.elementType != elementType)
return false;
long oldElements = elements;
// 与单个元素删除的逻辑类似
// 使用目标容器的elements取反然后做&操作达到批量删除的效果
elements &= ~es.elements;
return elements != oldElements;
}
判断元素是否存在
set的核心方法,contains有两个,一个是单个判断,一个是批量判断:
public boolean contains(Object e) {
if (e == null)
return false;
Class<?> eClass = e.getClass();
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false;
// 1左移目标单位,会形成只有该位置是1其他都是0的数据
// 0 与任何数&运算都得到0,因此最终结果就看目标位置是否为1,如果为1则结果必然不为0
return (elements & (1L << ((Enum<?>)e).ordinal())) != 0;
}
public boolean containsAll(Collection<?> c) {
if (!(c instanceof RegularEnumSet))
return super.containsAll(c);
RegularEnumSet<?> es = (RegularEnumSet<?>)c;
if (es.elementType != elementType)
return es.isEmpty();
// 类似单个判断的逻辑,取反后与目标数据进行&运算
return (es.elements & ~elements) == 0;
}
JumboEnumSet
JumboEnumSet在枚举数量超过64个的时候产生效果,其类内就不能使用单个long来存储数据了,而是一个使用一个long的数组来存储。那么在这个类中相比RegularEnumSet就是需要多上一步确定是第几号位置的long来处理。这里仅举个例子,其他方法大多差不多。
public boolean add(E e) {
typeCheck(e);
int eOrdinal = e.ordinal();
int eWordNum = eOrdinal >>> 6;
long oldElements = elements[eWordNum];
elements[eWordNum] |= (1L << eOrdinal);
boolean result = (elements[eWordNum] != oldElements);
if (result)
size++;
return result;
}
可以看到其中有一个步骤是向右位移6,实际上就是除以64的意思,获得到eWordNum来作为long数组的index。
总结
EnumSet是一个Abstract类不能实例化,他提供一系列的static方法获得其实现对象。实现类有两种,分别是分别是RegularEnumSet与JumboEnumSet,当枚举的个数小于64则使用RegularEnumSet,其内部使用一个字段long来存储数据,JumboEnumSet则使用long的数组进行存储数据。他们的操作基于位运算完成。