EnumSet的使用及源码分析

假设一种场景,如果你想用一个数表示多种状态,那么位运算是一种很好的选择。用或运算复合多种状态,用与运算判断是否包含某种状态。
由此,你可能会写出如下代码:

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数组


image.png

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);

你可能感兴趣的:(EnumSet的使用及源码分析)