Operatori Bitwise in Java
Manipola i singoli bit in Java con gli operatori &, |, ^, ~, <<, >> e >>> per flag, maschere e formati binari.
La maggior parte del codice Java non tocca i singoli bit. Ma ogni tanto — impacchettare flag in un int, leggere un formato di file binario, calcolare un hash, lavorare con bitmask di permessi — sarà necessario manipolare i valori a livello di bit. Gli operatori bitwise di Java sono il set in stile C: &, |, ^, ~ e i tre shift. Sono distinti dagli operatori logici && e ||, che usano il cortocircuito e funzionano solo su valori boolean — gli operatori bitwise agiscono su ogni bit di un intero.
Gli operatori
| Operatore | Nome | Cosa fa |
|---|---|---|
& | AND | il bit è 1 solo se entrambi i bit sono 1 |
| | OR | il bit è 1 se almeno uno dei due bit è 1 |
^ | XOR | il bit è 1 se i due bit sono diversi |
~ | NOT (complemento) | inverte ogni bit |
<< | shift a sinistra | sposta i bit a sinistra, riempie con 0 a destra |
>> | shift a destra con segno | sposta a destra, riempie con il bit del segno a sinistra |
>>> | shift a destra senza segno | sposta a destra, riempie con 0 a sinistra |
Tutti operano su operandi int e long. byte, short e char vengono promossi a int prima.
I letterali binari (0b...) rendono i pattern di bit facili da vedere:
int a = 0b1100; // 12
int b = 0b1010; // 10
System.out.println(Integer.toBinaryString(a & b)); // 1000 (8)
System.out.println(Integer.toBinaryString(a | b)); // 1110 (14)
System.out.println(Integer.toBinaryString(a ^ b)); // 110 (6)Nota che Integer.toBinaryString elimina gli zeri iniziali — 6 viene stampato come 110, non 0110. Se hai bisogno di una larghezza fissa per la visualizzazione, aggiungila manualmente.
NOT — ~
~ inverte ogni bit, incluso il bit del segno. Per un int a 32 bit, questo è il complemento a due: ~x equivale a -x - 1:
System.out.println(~0); // -1
System.out.println(~5); // -6
System.out.println(~-1); // 0Shift
<< sposta a sinistra, moltiplicando per potenze di 2:
System.out.println(1 << 0); // 1
System.out.println(1 << 1); // 2
System.out.println(1 << 4); // 16>> sposta a destra preservando il segno — utile per dividere interi con segno:
System.out.println(16 >> 2); // 4
System.out.println(-16 >> 2); // -4 — sign extended>>> sposta a destra e riempie sempre con zero — utile quando si tratta un int come bit senza segno:
System.out.println(-1 >>> 28); // 15
System.out.println(-1 >> 28); // -1Usi pratici
Bitmask di flag
Impacchetta diversi flag sì/no in un singolo int:
final int READ = 1 << 0; // 0001
final int WRITE = 1 << 1; // 0010
final int EXECUTE = 1 << 2; // 0100
int perms = READ | WRITE; // set both
boolean canRead = (perms & READ) != 0; // true
boolean canExecute = (perms & EXECUTE) != 0; // false
perms |= EXECUTE; // grant execute
perms &= ~WRITE; // revoke write
perms ^= READ; // toggle readÈ la stessa idea dei permessi sui file Unix.
Moltiplicare o dividere per potenze di 2
x << n equivale a x * 2ⁿ; x >> n equivale a x / 2ⁿ (per x non negativo):
int doubled = x << 1;
int halved = x >> 1;Il compilatore di solito ottimizza la moltiplicazione e la divisione semplice per potenze di 2 costanti in shift da solo, quindi scrivi quello che è più chiaro.
Scambiare due int senza una variabile temporanea
Un classico trucco XOR:
int a = 5, b = 3;
a ^= b;
b ^= a;
a ^= b;
System.out.println(a + " " + b); // 3 5Carino, ma raramente vale la pena usarlo rispetto a una variabile temporanea — i compilatori moderni gestiscono bene il caso con la variabile temporanea.
Una dimostrazione
Quando usare questi vs. EnumSet
Per un piccolo insieme fisso di flag nel Java moderno, EnumSet<MyFlag> è di solito più leggibile e altrettanto efficiente — memorizza i valori enum come una singola bitmask long internamente, quindi ottieni la leggibilità di Set<MyFlag> con operazioni veloci a livello di bit:
enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
perms.add(Permission.EXECUTE);
perms.contains(Permission.READ); // trueRicorri alle operazioni sui bit grezzi solo quando hai a che fare con formati binari, registri hardware, o percorsi critici dove l'impacchettamento in int è importante.
Cosa viene dopo
Java Strings — il tipo di riferimento con cui lavorerai più di qualsiasi altro.