位掩码本质是int或long整数,非独立类型;权限值须为2的幂,组合用按位或,检查用按位与并显式比较;推荐enumset替代手工位运算;bitcount仅适用于统计开启位数;数据库字段按权限数选int或bigint,超64项应重构。

位掩码本质是整数,不是特殊类型
Java里没有叫 Bitmask 的独立类型,它只是用 int 或 long 存储的一组二进制标志位。每个 bit 代表一个开关状态,比如第0位表示“读”,第1位表示“写”。误以为它是某种封装类,容易在序列化、JSON传输或数据库存取时出错——实际传的只是数字。
- 权限值必须是 2 的幂:
READ = 1(0b001)、WRITE = 2(0b010)、EXECUTE = 4(0b100),不能用3或5作基础权限值 - 组合权限用按位或:
READ | WRITE得到3(0b011),不是加法(虽然此时结果巧合相同,但语义和可维护性完全不同) - 检查权限用按位与:
(permissions & READ) == READ,不能写成permissions & READ后直接当布尔用——因为非零不等于true,Java 没有隐式转换
用 EnumSet 替代手工位运算更安全
纯位运算易错,尤其权限集变大后,手算 1 容易偏移、漏位、混淆顺序。Java 原生推荐用 <code>EnumSet ——它底层就是位向量实现,但接口友好、类型安全、可读性强。
- 定义权限枚举时,**顺序决定位位置**:
READ是第一个,就对应 bit 0;WRITE第二个,对应 bit 1,以此类推 -
EnumSet.of(Permission.READ, Permission.WRITE)自动打包成紧凑整数,EnumSet.noneOf()等价于0 - 跨服务传递时,别直接传
EnumSet对象(序列化兼容性差),应转为long:permissionSet.stream().mapToLong(p -> 1L a | b)
Integer.bitCount() 能快速统计开启权限数,但别滥用
需要判断用户有几个权限被启用时,Integer.bitCount(permissions) 比循环查每一位快得多,JVM 会映射到 CPU 的 popcnt 指令。但它只告诉你“开了几个”,不告诉你“开了哪些”。
- 仅适合做统计场景,比如“最多允许开启3项高级权限”,不适合做权限校验或动态菜单渲染
- 注意参数范围:
bitCount对int有效,若用long存权限,得用Long.bitCount(),混用会导致高位截断 - 不要为了“看起来高级”而强行用它替代明确的权限检查逻辑,例如
if (bitCount(perm) >= 2)不如if ((perm & READ) != 0 && (perm & WRITE) != 0)清晰
数据库字段类型选 BIGINT 还是 INT?看权限总数
一个 INT 最多支持 32 个独立权限(32 位),BIGINT 支持 64 个。别一上来就选 BIGINT——多数系统权限不会超 20 项,用 BIGINT 反而浪费空间、影响索引效率。
立即学习“Java免费学习笔记(深入)”;
- 如果确定权限数 ≤ 32,用
INT UNSIGNED(MySQL)或INTEGER(PostgreSQL),Java 侧对应int - 超过 32 项但不到 64 项,才升到
BIGINT+long;再往上就得考虑重构——位掩码已不适合,该换关系表或 JSON 字段了 - 迁移时注意:旧数据用
int存,升级后需补高位零扩展,否则(long)oldInt会符号扩展出错(如0x80000000转long变成负数)










