Operadores Bit a Bit en Java
Manipula bits individuales en Java con &, |, ^, ~, <<, >> y >>> operadores bit a bit.
La mayor parte del código Java no trabaja con bits individuales. Pero de vez en cuando — al empaquetar flags en un int, leer un formato de archivo binario, calcular un hash, o trabajar con máscaras de permisos — necesitarás manipular valores a nivel de bits. Los operadores bit a bit de Java son el conjunto de estilo C: &, |, ^, ~, y los tres desplazamientos. Son distintos de los operadores lógicos && y ||, que cortocircuitan y solo funcionan con valores boolean — los operadores bit a bit actúan sobre cada bit de un entero.
Los operadores
| Operador | Nombre | Qué hace |
|---|---|---|
& | AND | el bit es 1 solo si ambos bits son 1 |
| | OR | el bit es 1 si cualquiera de los bits es 1 |
^ | XOR | el bit es 1 si los dos bits son distintos |
~ | NOT (complemento) | invierte todos los bits |
<< | desplazamiento izquierdo | desplaza bits a la izquierda, rellena con 0 a la derecha |
>> | desplazamiento derecho con signo | desplaza a la derecha, rellena con el bit de signo a la izquierda |
>>> | desplazamiento derecho sin signo | desplaza a la derecha, rellena con 0 a la izquierda |
Todos operan sobre operandos int y long. Los tipos byte, short y char se promueven primero a int.
Los literales binarios (0b...) facilitan la visualización de los patrones de bits:
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)Ten en cuenta que Integer.toBinaryString elimina los ceros iniciales — 6 se imprime como 110, no como 0110. Si necesitas un ancho fijo para la visualización, deberás rellenarlo tú mismo.
NOT — ~
~ invierte todos los bits, incluido el bit de signo. Para int de 32 bits, eso es complemento a dos: ~x equivale a -x - 1:
System.out.println(~0); // -1
System.out.println(~5); // -6
System.out.println(~-1); // 0Desplazamientos
<< desplaza a la izquierda, multiplicando por potencias de 2:
System.out.println(1 << 0); // 1
System.out.println(1 << 1); // 2
System.out.println(1 << 4); // 16>> desplaza a la derecha preservando el signo — útil para dividir enteros con signo:
System.out.println(16 >> 2); // 4
System.out.println(-16 >> 2); // -4 — sign extended>>> desplaza a la derecha y siempre rellena con cero — tiene sentido cuando tratas un int como bits sin signo:
System.out.println(-1 >>> 28); // 15
System.out.println(-1 >> 28); // -1Usos prácticos
Máscaras de flags
Empaqueta varios flags de sí/no en un único 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 readEsta es la misma idea que los permisos de archivos en Unix.
Multiplicar o dividir por potencias de 2
x << n equivale a x * 2ⁿ; x >> n equivale a x / 2ⁿ (para x no negativo):
int doubled = x << 1;
int halved = x >> 1;El compilador normalmente optimizará la multiplicación y división simples por potencias de 2 constantes a desplazamientos por su cuenta, así que escribe lo que sea más claro.
Intercambiar dos ints sin variable temporal
Un truco clásico con XOR:
int a = 5, b = 3;
a ^= b;
b ^= a;
a ^= b;
System.out.println(a + " " + b); // 3 5Curioso, pero raramente vale la pena usarlo en lugar de una variable temporal — los compiladores modernos manejan bien el caso con variable temporal.
Una demostración
Cuándo usar estos vs. EnumSet
Para un conjunto pequeño y fijo de flags en Java moderno, EnumSet<MyFlag> suele ser más claro y igual de eficiente — almacena los valores enum como una única máscara de bits long internamente, por lo que obtienes la legibilidad de Set<MyFlag> con operaciones de velocidad bit a bit:
enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
perms.add(Permission.EXECUTE);
perms.contains(Permission.READ); // trueRecurre a las operaciones de bits en bruto solo cuando trabajes con formatos binarios, registros de hardware, o rutas críticas donde el empaquetado en int importa.
Qué sigue
Java Strings — el tipo de referencia con el que trabajarás más que con cualquier otro.