java enumset实现快眼,EnumSet的使用及源码分析

本文介绍了如何使用Java的EnumSet替代位运算进行状态管理,以提高代码的可读性和安全性。通过示例展示了EnumSet的创建、添加元素和判断元素是否存在等操作,并对比了位运算的实现方式。文章还深入解析了EnumSet的内部实现,包括getDeclaringClass方法的作用以及RegularEnumSet的存储机制。
摘要由CSDN通过智能技术生成

假设一种场景,如果你想用一个数表示多种状态,那么位运算是一种很好的选择。用或运算复合多种状态,用与运算判断是否包含某种状态。

由此,你可能会写出如下代码:

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是万无一失的

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

bba46c92431c

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,则表示存在该枚举值

有时,我们可能需要将这种复合状态的值记录下来,那么可以对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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值