在Review代码时候,看到一段涉及到USB的逻辑代码,他是这样写的
private boolean isUsbConnected; private boolean isUsbModeNCM; private boolean isUsbModeAccessory; private boolean isUsbModeAdb; private boolean isUsbModeMTP; ...
然后代码逻辑里是大量的成员变量的判断,显得非常臃肿而且难读懂,大量的if-else判断让代码逻辑很脆弱,稍微一个情况没考虑好就会出现难以排查的bug。
所以这种情况使用位掩码进行处理会更简单:
// 博客地址: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):
public void addUsbState(int flag) { mUsbState |= flag; }
假设添加accessory状态FLAG_USB_MODE_ACY
0000 |= 0100 -> 0100
所以mUsbState就是0100的状态了。
继续添加FLAG_USB_MODE_ADB状态
0100 |= 1000 -> 1100
也可以一次添加多个状态,比如上面的两个状态在一次设置同时添加:
addUsbState(FLAG_USB_MODE_ACY | FLAG_USB_MODE_ADB);
结果就是:
0000 |= (0100 | 1000) -> 0000 |= 1100 -> 1100
如果是原来的boolean变量,就需要单独为每一个变量设置,就会很麻烦。
public void removeUsbState(int flag) { mUsbState &= ~flag; }
比如接着上面移除FLAG_USB_MODE_ADB状态
1100 &= ~1000 -> 1100 &= 0111 -> 0100
如果移除一个不存在的状态比如FLAG_USB_MODE_NCM
0100 &= ~0010 -> 0100 &= 1101 -> 0100
可以看到并不会对当前状态造成任何影响。
最后看一下 检查状态 :
public boolean isUsbStateEnable(int flag) { return (mUsbState & flag) == flag; }
首先检查一下当前拥有的状态:
(0100 & 0100) == 0100 -> 0100 == 0100 -> true
可以检测到该状态。然后换一个状态:
(0100 & 1000) == 1000 -> 0000 == 1000 -> false
没有检测到该状态。
所以,通过三个简单的方法,就可以检查一个变量里保存的所有状态,避免了使用大量bool变量进行挨个检查。简化了代码,增加代码可读性,并且使代码更加稳定。
到这里你可能觉得问题解决了就完了,但是还没有!
实际上,《Effective Java》这本书有对位域的一项讨论:
位域表示法也允许利用位操作,有效的执行像union和intersection这样的集合操作。但位域有着int枚举常量所有的缺点,甚至更多。当位域以数字形式打印时,翻译位域比翻译简单的int枚举常量要困难很多。甚至要遍历位域表示的所有元素也没有很容易的方法。
Java.util包提供了EnumSet类来有效地表示从单个枚举类型中提取的多个值的多个集合。这个类实现Set 接口 ,提供丰富的功能、类型安全性以及可从其他Set实现中得到的互用性。
内部实现上,每个EnumSet内容都表示为位矢量,一般(低于64个元素)整个EnumSet就是用一个long的位运算来表示的。也就是说它替你使用位算法实现了这一切,避免你自己写位运算导致代码难读懂的情况。
下面是用EnumSet修改后的示例代码,它更加简短,清楚也更安全。
// 博客地址: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); } }
测试用例以及输出
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)); }
输出为
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]
综上,代码唯一要注意的是
public boolean checkFlagEnabled(Set<UsbFlags> flag)
传入参数使用了Set接口,这是考虑到可能会传入其他Set的实现类型,所以传入接口参数要好于实现类型参数。
最后,EnumSet类集成了位域自身的简洁性和性能优势,又拥有枚举的所有优点,所以使用它代替位域是非常好的选择。