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);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容

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