假設一種場景,如果你想用一個數表示多種狀態,那么位運算是一種很好的選擇。用或運算復合多種狀態,用與運算判斷是否包含某種狀態。
由此,你可能會寫出如下代碼:
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> status) { ... }
}
wrapper.setStatus(EnumSet.of(Status.IN_STORED, Status.ON_THE_WAY));
那么,使用EnumSet的好處是:
1、避免手動操作位運算可能出現的錯誤
2、代碼更簡短、清晰、安全
原理:
// EnumSet.java
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;
}
初始化Enum Set。還提供了其他5個of靜態函數,分別是不同參數個數,需要注意的是變長參數那個of函數可能會更慢些
// Enum.java
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)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 <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);
}
noneOf函數會創建一個空枚舉set:
1、getUniverse獲取Enum數組
2、 根據數組長度,小于等于64則返回RegularEnumSet,否則JumboEnumSet
RegularEnumSet是EnumSet的子類,RegularEnumSet的構造函數中會調用EnumSet的構造函數,將枚舉類型、枚舉數組保存起來:
/**
* The class of all the elements of this set.
*/
final Class<E> 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<Type> 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);