前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Java] 使用EnumSet代替位运算简化代码逻辑

[Java] 使用EnumSet代替位运算简化代码逻辑

作者头像
wOw
发布2020-01-20 17:11:11
1.9K0
发布2020-01-20 17:11:11
举报
文章被收录于专栏:wOw的Android小站wOw的Android小站

位运算

在Review代码时候,看到一段涉及到USB的逻辑代码,他是这样写的

代码语言:javascript
复制
private boolean isUsbConnected;
private boolean isUsbModeNCM;
private boolean isUsbModeAccessory;
private boolean isUsbModeAdb;
private boolean isUsbModeMTP;
...

然后代码逻辑里是大量的成员变量的判断,显得非常臃肿而且难读懂,大量的if-else判断让代码逻辑很脆弱,稍微一个情况没考虑好就会出现难以排查的bug。

所以这种情况使用位掩码进行处理会更简单:

代码语言:javascript
复制
// 博客地址:wossoneri.github.io
private static final int FLAG_USB_CONNECTED = 0x1;
private static final int FLAG_USB_MODE_NCM = 0x1 << 1;
private static final int FLAG_USB_MODE_ACY = 0x1 << 2;
private static final int FLAG_USB_MODE_ADB = 0x1 << 3;
private static final int FLAG_USB_MODE_MTP = 0x1 << 4;
...
private int mUsbState;

public void addUsbState(int flag) {
 	mUsbState |= flag; 
}

public void removeUsbState(int flag) {
  mUsbState &= ~flag;
}

public boolean isUsbStateEnable(int flag) {
  return (mUsbState & flag) == flag;
}

简单分析一下这样写的好处:

FLAG_USB_CONNECTED = 0001 FLAG_USB_MODE_NCM = 0010 FLAG_USB_MODE_ACY = 0100 FLAG_USB_MODE_ADB = 1000

通过移位,使得每一位都有独立的代表的意义,1代表enable,0代表disable。

如果要添加状态(Java里int值默认赋值为0):

代码语言:javascript
复制
public void addUsbState(int flag) {
 	mUsbState |= flag; 
}

假设添加accessory状态FLAG_USB_MODE_ACY

代码语言:javascript
复制
0000 |= 0100 -> 0100

所以mUsbState就是0100的状态了。

继续添加FLAG_USB_MODE_ADB状态

代码语言:javascript
复制
0100 |= 1000 -> 1100

也可以一次添加多个状态,比如上面的两个状态在一次设置同时添加:

代码语言:javascript
复制
addUsbState(FLAG_USB_MODE_ACY | FLAG_USB_MODE_ADB);

结果就是:

代码语言:javascript
复制
0000 |= (0100 | 1000)
-> 0000 |= 1100
-> 1100

如果是原来的boolean变量,就需要单独为每一个变量设置,就会很麻烦。


然后是移除状态

代码语言:javascript
复制
public void removeUsbState(int flag) {
  mUsbState &= ~flag;
}

比如接着上面移除FLAG_USB_MODE_ADB状态

代码语言:javascript
复制
1100 &= ~1000
-> 1100 &= 0111
-> 0100

如果移除一个不存在的状态比如FLAG_USB_MODE_NCM

代码语言:javascript
复制
0100 &= ~0010
-> 0100 &= 1101
-> 0100

可以看到并不会对当前状态造成任何影响。


最后看一下检查状态

代码语言:javascript
复制
public boolean isUsbStateEnable(int flag) {
  return (mUsbState & flag) == flag;
}

首先检查一下当前拥有的状态:

代码语言:javascript
复制
(0100 & 0100) == 0100
-> 0100 == 0100
-> true

可以检测到该状态。然后换一个状态:

代码语言:javascript
复制
(0100 & 1000) == 1000
-> 0000 == 1000
-> false

没有检测到该状态。

所以,通过三个简单的方法,就可以检查一个变量里保存的所有状态,避免了使用大量bool变量进行挨个检查。简化了代码,增加代码可读性,并且使代码更加稳定。

进阶!使用EnumSet替代位运算

到这里你可能觉得问题解决了就完了,但是还没有!

实际上,《Effective Java》这本书有对位域的一项讨论:

位域表示法也允许利用位操作,有效的执行像union和intersection这样的集合操作。但位域有着int枚举常量所有的缺点,甚至更多。当位域以数字形式打印时,翻译位域比翻译简单的int枚举常量要困难很多。甚至要遍历位域表示的所有元素也没有很容易的方法。

Java.util包提供了EnumSet类来有效地表示从单个枚举类型中提取的多个值的多个集合。这个类实现Set接口,提供丰富的功能、类型安全性以及可从其他Set实现中得到的互用性。

内部实现上,每个EnumSet内容都表示为位矢量,一般(低于64个元素)整个EnumSet就是用一个long的位运算来表示的。也就是说它替你使用位算法实现了这一切,避免你自己写位运算导致代码难读懂的情况。

下面是用EnumSet修改后的示例代码,它更加简短,清楚也更安全。

代码语言:javascript
复制
// 博客地址:wossoneri.github.io
public class UsbManager {

    private EnumSet<UsbFlags> mUsbState = EnumSet.noneOf(UsbFlags.class);

    public enum UsbFlags {
        CONNECTED, NCM, ACCESSORY, ADB, MTP
    }

    public void addFlag(UsbFlags flag) {
        mUsbState.add(flag);
        System.out.println("After add flag " + flag + ", Now state is " + this.mUsbState);
    }

    public void addFlag(Set<UsbFlags> flags) {
        mUsbState.addAll(flags);
        System.out.println("After add flags " + flags + ", Now state is " + this.mUsbState);
    }

    public void removeFlag(UsbFlags flag) {
        mUsbState.remove(flag);
        System.out.println("After remove flag " + flag + ", Now state is " + this.mUsbState);
    }

    public void removeFlag(Set<UsbFlags> flags) {
        mUsbState.removeAll(flags);
        System.out.println("After remove flags " + flags + ", Now state is " + this.mUsbState);
    }

    public boolean checkFlagEnabled(UsbFlags flag) {
        return mUsbState.contains(flag);
    }

    public boolean checkFlagEnabled(Set<UsbFlags> flag) {
        return mUsbState.containsAll(flag);
    }

    public void printUsbState() {
        System.out.println("Current usb state is " + mUsbState);
    }
}

测试用例以及输出

代码语言:javascript
复制
public static void main(String[] args) {
  // 博客地址:wossoneri.github.io
  UsbManager usbManager = new UsbManager();
  usbManager.printUsbState();
  // 添加一项flag
  usbManager.addFlag(UsbFlags.CONNECTED);
  // 添加一组 flag
  usbManager.addFlag(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB));

  // 检查存在的一个flag
  System.out.println("mUsbState contains flag " + UsbFlags.ACCESSORY + ": " +
                     usbManager.checkFlagEnabled(UsbFlags.ACCESSORY));
  // 检查不存在的一个flag
  System.out.println("mUsbState contains flag " + UsbFlags.MTP + ": " +
                     usbManager.checkFlagEnabled(UsbFlags.MTP));
  // 检查一组存在的flag
  System.out.println("mUsbState contains flag " + EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB) + ": " +
                     usbManager.checkFlagEnabled(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB)));
  // 检查一组包含不存在的flag
  System.out.println("mUsbState contains flag " + EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.MTP) + ": " +
                     usbManager.checkFlagEnabled(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.MTP)));
  // 检查一组都不存在的flag
  System.out.println("mUsbState contains flag " + EnumSet.of(UsbFlags.NCM, UsbFlags.MTP) + ": " +
                     usbManager.checkFlagEnabled(EnumSet.of(UsbFlags.NCM, UsbFlags.MTP)));

  usbManager.printUsbState();

  // 删除一个不存在的flag
  usbManager.removeFlag(UsbFlags.MTP);
  // 删除一个存在的flag
  usbManager.removeFlag(UsbFlags.ACCESSORY);
  // 删除一组都不存在的flag
  usbManager.removeFlag(EnumSet.of(UsbFlags.NCM, UsbFlags.MTP));
  // 删除一组包含不存在的flag
  usbManager.removeFlag(EnumSet.of(UsbFlags.NCM, UsbFlags.ADB));

  usbManager.addFlag(EnumSet.of(UsbFlags.ACCESSORY, UsbFlags.ADB));
  // 删除一组存在的flag
  usbManager.removeFlag(EnumSet.of(UsbFlags.ADB, UsbFlags.ACCESSORY));
}

输出为

代码语言:javascript
复制
Current usb state is []
After add flag CONNECTED, Now state is [CONNECTED]
After add flags [ACCESSORY, ADB], Now state is [CONNECTED, ACCESSORY, ADB]
mUsbState contains flag ACCESSORY: true
mUsbState contains flag MTP: false
mUsbState contains flag [ACCESSORY, ADB]: true
mUsbState contains flag [ACCESSORY, MTP]: false
mUsbState contains flag [NCM, MTP]: false
Current usb state is [CONNECTED, ACCESSORY, ADB]
After remove flag MTP, Now state is [CONNECTED, ACCESSORY, ADB]
After remove flag ACCESSORY, Now state is [CONNECTED, ADB]
After remove flags [NCM, MTP], Now state is [CONNECTED, ADB]
After remove flags [NCM, ADB], Now state is [CONNECTED]
After add flags [ACCESSORY, ADB], Now state is [CONNECTED, ACCESSORY, ADB]
After remove flags [ACCESSORY, ADB], Now state is [CONNECTED]

综上,代码唯一要注意的是

代码语言:javascript
复制
public boolean checkFlagEnabled(Set<UsbFlags> flag)

传入参数使用了Set接口,这是考虑到可能会传入其他Set的实现类型,所以传入接口参数要好于实现类型参数。

最后,EnumSet类集成了位域自身的简洁性和性能优势,又拥有枚举的所有优点,所以使用它代替位域是非常好的选择。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 位运算
  • 进阶!使用EnumSet替代位运算
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档