Operadores bit a bit em Java

Operadores bit a bit em Java

Na carreira de desenvolvedor, seja back-end ou front-end, não é muito comum a utilização de operadores bit a bit, porém conhecê-los é parte importante para a resolução de muitos problemas. Neste artigo conheceremos o conceito e avaliaremos uma aplicação prática.

Na carreira de desenvolvedor, seja back-end ou front-end, não é muito comum a utilização de operadores bit a bit. Porém, conhecê-los é parte importante para a resolução de muitos problemas. Neste artigo conheceremos o conceito e avaliaremos uma aplicação prática.

Os operadores bit a bit (bitwise) são utilizados para a manipulação individual dos bits de um número inteiro (byte, short, int e long). Vamos analisá-los para entender como cada um funciona.

Operador AND (&)

O operador & retorna 1 se, e somente se, as entradas a e b forem 1.

aba & b
000
010
100
111

Exemplo:

int a = 3; // 0011 (em binário)
int b = 5; // 0101 (em binário)
System.out.println(a & b); // exibe 1

  0011
& 0101
------
  0001

Operador OR (|)

O operador | retorna 1 se a entrada a ou b possuir o valor 1.

aba | b
000
011
101
111

Exemplo:

int a = 3; // 0011 (em binário)
int b = 5; // 0101 (em binário)
System.out.println(a | b); // exibe 7

  0011
| 0101
------
  0111

Operador XOR (^)

O operador ^ retorna 1 se a entrada a for diferente da entrada b.

aba ^ b
000
011
101
110

Exemplo:

int a = 3; // 0011 (em binário)
int b = 5; // 0101 (em binário)
System.out.println(a ^ b); // exibe 6

  0011
^ 0101
------
  0110

Operador Complemento (~)

O operador ~ inverte cada bit da entrada: 1 torna-se 0 e 0 torna-se 1.

a~a
01
10

Exemplo:

int a = 3; // 0011 (em binário)
System.out.println(~a); // exibe -4

~ 0011
------
  1100

Um detalhe importante: 1100 em binário sem sinal equivale a 12 em decimal, porém o resultado exibido é -4. Isso ocorre porque a JVM utiliza a representação em complemento de dois, onde o bit mais à esquerda indica o sinal e os bits restantes representam o valor.

Operador de deslocamento à direita com sinal (»)

O operador >> desloca os bits do número para a direita, preenchendo as posições à esquerda com o bit de sinal — 0 para números positivos e 1 para negativos.

Exemplo:

int a = 6; // 0110 (em binário)
System.out.println(a >> 1); // desloca 1 bit à direita, exibe 3

>> 0110
-------
   0011

int b = -4; // 11111111 11111111 11111111 11111100 (em binário, complemento de dois)
System.out.println(b >> 1); // desloca 1 bit à direita, exibe -2

>> 11111111 11111111 11111111 11111100
---
   11111111 11111111 11111111 11111110

O efeito desse operador é equivalente a dividir a entrada por uma potência de 2. Nos exemplos acima, deslocar 1 bit equivale a dividir por 2¹.

Operador de deslocamento à direita sem sinal (»>)

O operador >>> funciona como o >>, mas sempre preenche com 0 as posições à esquerda, independentemente do sinal.

Exemplo:

int b = -4;
System.out.println(b >>> 1); // exibe 2147483646

Operador de deslocamento à esquerda («)

O operador << desloca os bits do número para a esquerda, preenchendo as posições à direita com 0. O efeito é equivalente a multiplicar o valor por uma potência de dois.

Exemplo:

int a = 3; // 0011 (em binário)
System.out.println(a << 1); // desloca 1 bit à esquerda, exibe 6

<< 0011
-------
   0110

int b = -2; // 11111111 11111111 11111111 11111110 (em binário)
System.out.println(b << 1); // desloca 1 bit à esquerda, exibe -4

<< 11111111 11111111 11111111 11111110
---
   11111111 11111111 11111111 11111100

Aplicação prática

Agora que compreendemos os operadores bit a bit, vamos criar uma classe que representa cores no padrão RGBA (Red, Green, Blue e Alpha).

Cada componente de uma cor RGBA é um valor de intensidade entre 0 e 255, que pode ser armazenado em 8 bits. Como são quatro componentes, precisamos de 32 bits no total — exatamente o tamanho do tipo primitivo int em Java.

Para usar uma única variável, particionamos seus 32 bits em quatro partes de 8 bits: a primeira armazena Red, a segunda Green, a terceira Blue e a quarta Alpha.

00000000  00000000  00000000  00000000
   RED     GREEN      BLUE     ALPHA

Vejamos o código:

public class Color {
    private final int value;

    private Color(int r, int g, int b, int a) {
        this.value = ((r << 24) | (g << 16) | (b << 8) | a);
    }
}

A instrução de atribuição no construtor pode ser entendida passo a passo:

int r = 100; // 00000000 00000000 00000000 01100100
int g = 150; // 00000000 00000000 00000000 10010110
int b = 200; // 00000000 00000000 00000000 11001000
int a = 255; // 00000000 00000000 00000000 11111111

// Passo 1: desloca r 24 bits à esquerda
(r << 24) // 01100100 00000000 00000000 00000000
// Passo 2: desloca g 16 bits à esquerda
(g << 16) // 00000000 10010110 00000000 00000000
// Passo 3: combina r e g com OR
r | g     // 01100100 10010110 00000000 00000000
// Passo 4: desloca b 8 bits à esquerda
(b << 8)  // 00000000 00000000 11001000 00000000
// Passo 5: combina com b
t | b     // 01100100 10010110 11001000 00000000
// Passo 6: combina com a (sem deslocamento)
t | a     // 01100100 10010110 11001000 11111111

Agora os métodos para extrair cada componente a partir de value:

Para Red, deslocamos 24 bits à direita sem sinal:

public int red() {
    return this.value >>> 24;
}

Para Green e Blue, além do deslocamento, aplicamos & 0xFF para zerار os bits dos componentes à esquerda, preservando apenas os 8 bits relevantes:

public int green() {
    return (this.value >>> 16) & 0xFF;
}

public int blue() {
    return (this.value >>> 8) & 0xFF;
}

Para Alpha, basta aplicar & 0xFF diretamente, sem deslocamento:

public int alpha() {
    return this.value & 0xFF;
}

A classe completa:

public class Color {

    private final int value;

    private Color(int r, int g, int b, int a) {
        validateRange(r, g, b, a);
        this.value = ((r << 24) | (g << 16) | (b << 8) | a);
    }

    public int red() {
        return this.value >>> 24;
    }

    public int green() {
        return (this.value >>> 16) & 0xFF;
    }

    public int blue() {
        return (this.value >>> 8) & 0xFF;
    }

    public int alpha() {
        return this.value & 0xFF;
    }

    public String toHTML() {
        return String.format("#%X", value);
    }

    private void validateRange(int r, int g, int b, int a) {
        String component = "";
        if (r < 0 || r > 255) component += " Red";
        if (g < 0 || g > 255) component += " Green";
        if (b < 0 || b > 255) component += " Blue";
        if (a < 0 || a > 255) component += " Alpha";
        if (!component.isEmpty()) {
            throw new IllegalArgumentException(
              "Color parameter outside of expected range:" + component);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Color color = (Color) o;
        return value == color.value;
    }

    @Override
    public int hashCode() {
        return value;
    }

    @Override
    public String toString() {
        return String.format(
          "Color {Red = %d, Green = %d, Blue = %d, Alpha = %d, Hexadecimal = %s}",
          red(), green(), blue(), alpha(), toHTML());
    }

    public static Color rgba(int r, int g, int b, int a) {
        return new Color(r, g, b, a);
    }

    public static Color rgb(int r, int g, int b) {
        return new Color(r, g, b, 0xFF);
    }
}

Conclusão

Conhecer os operadores bit a bit é parte fundamental no desenvolvimento de software. Eles são úteis em diversas situações onde manipular individualmente os bits de um valor é mais prático, mais performático ou simplesmente a única abordagem viável para resolver o problema.