You knew this kind of answer had to appear sooner or later, so here it goes:
Although the usage of bitmasks is arguably the fastest and has the lowest memory consuption of all the alternative options, it's also very error prone to get right and it's mostly discouraged to use except in some very edge cases. It's a classic low-level tool. If done right, works wonders, if misused, can wreak havoc.
Therefore, the correct approach is to use higher-level abstractions for this, namely enums
and EnumSets
. The speed and memory consuption are comparable, although slightly worse, of course. In a general case, they're absolutely sufficient, though. There are many ways of how to do that based on your exact context and needs. One of the possibilities could be this:
public enum Permission {
BUILD, BREAK, INTERACT;
}
public class Permissions {
private final Set<Permission> alliance = EnumSet.noneOf(Permission.class);
private final Set<Permission> outsiders = EnumSet.noneOf(Permission.class);
public Set<Permission> alliance() {
return alliance;
}
public Set<Permission> outsiders() {
return outsiders;
}
}
This alone enables you to do exactly the things you did, but with two differences:
- Now it's type-safe and more foolproof, I think. No reinventing of the wheel needed.
- It uses more memory. Not by much, because
EnumSet
this small is usually just a long
.
EDIT to answer OP's comment on storing the EnumSet to a database:
Yes, this can be a concern since storing an int
is infinitely easier. If you still considered sticking to using an EnumSet
, then there are several possibilities from the top of my head:
Look at SO. People have tried to tackle the problem before.
Save the names of the values in the EnumSet
:
Permissions p = new Permissions();
p.alliance().addAll(EnumSet.of(Permission.BREAK, Permission.BUILD));
for (Permission permission : p.alliance()) {
System.out.println(permission);
}
You can then easily rebuild the values:
for (String word : stringsFromDtb) {
p.alliance.add(Permission.valueOf(word));
}
Save the ordinals. This is VERY dangerous as you can easily break it with changing the Permission
enum. Also, any random number could be fed in to break this.
Permissions p = new Permissions();
p.alliance().addAll(EnumSet.of(Permission.BREAK, Permission.BUILD));
for (Permission permission : p.alliance()) {
System.out.println(permission.ordinal());
}
You can then easily rebuild the values:
for (int ordinal : ordinalsFromDtb) {
p.alliance.add(Permission.values()[ordinal]);
}
Serialize the EnumSet
the usual way and store the binary data directly or BASE64ed. Ehm.
---
EDIT after EDIT:
Ad. your remarks of making indexes for your enum
values so that when you changed or reordered them in the future, it would still work. There is an easy way to do this with enums
! It's basically the middle way between bitfields and enums
, it retains the type safety and all enum
features and still has the advantages of bitfields.
public enum Permission {
/* I like to have binary literals in place of bit fields,
* but any notation will work */
BUILD (0b0001),
BREAK (0b0010),
INTERACT(0b0100);
private final int index;
private Permission(int index) {
this.index = index;
}
public int index() {
return index;
}
}
You'll then save the indexes into your database and will only have to make sure the parsing from it is right. Also, in the future, it helps to only comment out (not delete) any unneeded enum values so they still remain visible for you and you won't take up it's index. Or just mark it as @Deprecated
and you don't have to delete anything ;).