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


image.png

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);
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、什么是枚举? 枚举是由一组固定的常量组成的合法值。通过这一定义,我们可以看出枚举的核心在于常量,而且常量是固定...
    Roger_黄阅读 1,034评论 0 4
  • Chapter 6 Enums and Annotations 枚举和注解 JAVA supports two s...
    LaMole阅读 870评论 0 2
  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 2,045评论 0 3
  • 在公众号“陈珂”上,看到名为《应该从哪里开始学习?怎样学习》的一篇文章。 全文有三个标题,第三个标题是“掌握黄金思...
    思慕海阅读 404评论 2 1
  • 蒙山浪子阅读 132评论 0 1