假设一种场景,如果你想用一个数表示多种状态,那么位运算是一种很好的选择。用或运算复合多种状态,用与运算判断是否包含某种状态。
由此,你可能会写出如下代码:
public class Status {
public static final int IN_STORED = 1 << 0; // 1,在仓
public static final int ON_THE_WAY = 1 << 1; // 2,在途
private int value;
public void setStatus(int value) { this.value = value; }
public int getStatus() { return value; }
}
status.setStatus(IN_STORED | ON_THE_WAY); // 设置为即在仓也在途
IN_STORED & status.getStatus() > 0 ? // 判断状态中是否包含在仓
但是Java有EnumSet,可以优化为:
public class StatusWrapper {
public enum Status { IN_STORED, ON_THE_WAY }
public void setStatus(Set status) { ... }
}
wrapper.setStatus(EnumSet.of(Status.IN_STORED, Status.ON_THE_WAY));
那么,使用EnumSet的好处是:
1、避免手动操作位运算可能出现的错误
2、代码更简短、清晰、安全
原理:
// EnumSet.java
public static > EnumSet of(E e1, E e2) {
EnumSet result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
return result;
}
初始化Enum Set。还提供了其他5个of静态函数,分别是不同参数个数,需要注意的是变长参数那个of函数可能会更慢些
// Enum.java
@SuppressWarnings("unchecked")
public final Class getDeclaringClass() {
Class> clazz = getClass();
Class> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class)clazz : (Class)zuper;
}
问:为什么要定义getDeclaringClass,而不直接使用getClass呢?
答:先看如下例子:
public enum MyEnum {
A {
void doSomething() { ... }
},
B {
void doSomethingElse() { ... }
};}
现象:MyEnum.A.getClass()和MyEnum.A.getDeclaringClass()是不一样的。
原因:Java enum values are permitted to have value-specific class bodies(Java枚举值允许有特定于值的类主体),这将生成表示A和B的类主体的内部类。这些内部类将是MyEnum的子类。因此MyEnum.A.getClass()返回的是表示A类主体(A's class body)的匿名类,而MyEnum.A.getDeclaringClass()会返回MyEnum。
结论:如果是简单的枚举(without constant-specific class bodies),这2种方法返回的结果是一样的,但如果枚举包含constant-specific class bodies,就会出现不一致。因此对枚举类进行比较的时候,使用getDeclaringClass是万无一失的
参考资料: https://stackoverflow.com/questions/5758660/java-enum-getdeclaringclass-vs-getclass
// EnumSet.java
// Creates an empty enum set with the specified element type
public static > EnumSet noneOf(Class 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);
}
noneOf函数会创建一个空枚举set:
1、getUniverse获取Enum数组
2、 根据数组长度,小于等于64则返回RegularEnumSet,否则JumboEnumSet
RegularEnumSet是EnumSet的子类,RegularEnumSet的构造函数中会调用EnumSet的构造函数,将枚举类型、枚举数组保存起来:
/**
* The class of all the elements of this set.
*/
final Class elementType;
/**
* All of the values comprising T. (Cached for performance.)
*/
final Enum>[] universe;
下面只选取RegularEnumSet的源码进行分析:
// RegularEnumSet.java
public boolean add(E e) {
typeCheck(e);
long oldElements = elements;
elements |= (1L << ((Enum>)e).ordinal());
return elements != oldElements;
}
1)typeCheck函数校验传入的e是否是该枚举中的值
2)重点是:elements |= (1L << ((Enum>)e).ordinal());
2.1)private long elements = 0L;
2.2)取枚举值的ordinal,初始为0;( 每个枚举值都对应着自己的ordinal,第一个枚举值的ordinal为0,第二个为1,以此类推。ordinal()返回的是ordinal的值)
2.3)1 << ordinal ;
2.4)与elements做 |=运算。
例:
a. 添加ordinal为0的枚举值,则计算后elements为1 (B)
b. 添加ordinal为1的枚举值,则计算后elements为( 01 | 10 ) = 11 (B)
c. 添加ordinal为2的枚举值,则计算后elements为(011 | 100) = 111 (B)
换句话说,long的最低位为1,表示存储了ordinal为0的枚举值,次低位为1,表示存储了ordinal为1的枚举值,以此类推。
所以,RegularEnumSet 就是用long型来存储枚举,支持64位(long64位)。
// RegularEnumSet.java
public boolean contains(Object e) {
if (e == null)
return false;
Class> eClass = e.getClass();
if (eClass != elementType && eClass.getSuperclass() != elementType)
return false;
return (elements & (1L << ((Enum>)e).ordinal())) != 0;
例:假设RegularEnumSet已存储了ordinal为0,1,2的枚举值 ,判断ordinal为1的枚举值是否已存储。
1、1L << ((Enum>)e).ordinal() = 010 (B)
2、elements为111 (B)
3、elements & 10 = 010 (B)不等于0,则表示存在该枚举值
参考资料:https://blog.csdn.net/u010887744/article/details/50834738
有时,我们可能需要将这种复合状态的值记录下来,那么可以对Type进行改造:
public enum Type {
IN_STORED(1 << 0, "在仓"),
ON_THE_WAY(1 << 1, "在途"),
;
private Integer value;
private String desc;
Type(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
public Integer getValue() {
return value;
}
public String getDesc() {
return desc;
}
/**
* 计算Set集合的value和
*/
public static Integer valueOf(Set types) {
Integer value = 0;
for (Type type : types) {
value += type.getValue();
}
return value;
}
/**
* 判断value是否包含type
*/
public static Boolean contains(Integer value, Type type) {
return (value & type.getValue()) > 0;
}
}
// 外部调用:
int value = Type.valueOf(EnumSet.of(Type.IN_STORED, Type.ON_THE_WAY));
Boolean contains = Type.contains(value, Type.IN_STORED);