Operadores bit à bit em Java
Na carreira de desenvolvedor web, seja back-end ou front-end não é muito comum a utilização de operadores bit à bit, porém conhecê-los é parte importante para resolução de muitos problemas. Neste artigo iremos conhecer o conceito e iremos avaliar uma aplicação prática.
Os operadores bit à bit (bitwise) são utilizados para a manipulação de maneira individual dos bits de um número inteiro (byte, short, int e long), vamos analisá-los para saber como cada um funciona.
Operador AND (&)
O operador &
retorna 1 se, e somente se, as entradas a
e b
forem 1.
a | b | a & b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
Exemplo,
int a = 3; // 0011 (em binário)
int b = 5; // 0101 (em binário)
System.out.println(a & b); // exibe o resultado 1
0011
& 0101
------
0001
Operador OR (|)
O operador |
retorna 1 se a entrada a
ou b
possuir o valor 1.
a | b | a | b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
Exemplo,
int a = 3; // 0011 (em binário)
int b = 5; // 0101 (em binário)
System.out.println(a & b); // exibe o resultado 7
0011
& 0101
------
0111
Operador XOR (^)
O operador ^
retorna 1 se a entrada a
for diferente da entrada b
.
a | b | a ^ b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Exemplo,
int a = 3; // 0011 (em binário)
int b = 5; // 0101 (em binário)
System.out.println(a ^ b); // exibe o resultado 6
0011
& 0101
------
0110
Operador Complemento (~)
O operador ~
inverte a entrada, ou seja, se a entrada for 1 o resultado é 0, caso seja 0 o resultado é 1.
a | ~a |
---|---|
0 | 1 |
1 | 0 |
Exemplo,
int a = 3; // 0011 (em binário)
System.out.println(~a); // exibe o resultado -4
~ 0011
------
1100
Um detalhe importante nesse exemplo é que 1100 em decimal é 12, porém o resultado mostrado é -4. Isso acontece porque a JVM utiliza a representação complemento de 2, portanto o bit mais a esquerda representa o sinal e, os bits restantes, o valor.
Operador deslocamento à direita com sinal (>>)
O operador >>
desloca os bits do número para a direita e preenche com 0 os vazios deixados no resultado. Esse operador mantém o sinal, logo o bit mais a esquerda dependerá do sinal do número de entrada.
Exemplo,
int a = 6 // 0110 (em binário)
System.out.println(a >> 1); // desloca 1 bit à direita e exibe o resultado 3
>> 0110
-------
0011
int b = -4 // 1100 (em binário)
System.out.println(b >> 1); // desloca 1 bit à direita e exibe o resultado -2
>> 1100
-------
1010
O efeito desse operador é semelhante a dividir a entrada pela potência de 2, nos exemplos acima obtemos o mesmo resultado dividindo a entrada por 2¹.
Operador deslocamento à direita sem sinal (>>>)
O operador >>>
é semelhante ao operador >>
, porém não mantém o sinal do número de entrada.
Exemplo,
int b = -4 // 1100 (em binário)
System.out.println(b >>> 1); // exibe o resultado 2147483646
Operador deslocamento à esquerda (<<)
O operador <<
desloca os bits do número à esquerda e preenche com 0 os vazios deixados no resultado. O efeito desse operador é o mesmo que multiplicar o valor da entrada pela potência de dois.
Exemplo,
int a = 3 // 0011 (em binário)
System.out.println(a << 1); // desloca 1 bit à esquerda e exibe o resultado 6
<< 0011
-------
0110
int b = -2 // 1010 (em binário)
System.out.println(b << 1); // desloca 1 bit à esquerda e exibe o resultado -4
<< 1010
-------
1100
Aplicação prática
Agora que já analisamos e compreendemos os conceitos dos operadores bit à bit, vamos criar uma classe que representa cores no padrão RGBA (RED, GREEN, BLUE e ALPHA). Sabemos que, cada componente de uma cor no padrão RBGA é um número de intensidade que varia entre 0 e 255. Podemos então armazenar esse intervalo em oito bits de dados, como são quatro componentes, precisaremos de 32 bits de armazenamento. Na linguagem Java temos o tipo primitivo int que comporta exatamente os 32 bits que precisamos.
Para utilizar uma única variável, precisamos particioná-la em quatro partes de oito bits, na primeira partição iremos armazenar os valores do componente RED, na segunda o componente GREEN, na terceira o componente BLUE e na última o componente ALPHA.
00000000 00000000 00000000 00000000
RED GREEN BLUE ALPHA
Vamos ao código,
public class Color {
private final int value; // armazena o valor da cor
private Color(int r, int g, int b, int a) {
this.value = ((r << 24) | (g << 16) | (b << 8) | a);
}
}
Criamos uma classe Color que contém um construtor que recebe os quatro componentes. A primeira linha do construtor faz a atribuição do valor. Vamos analisar passo a passo no que ocorre na instrução de atribuição:
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
(r << 24) // r = 01100100 00000000 00000000 00000000
// Passo 2
(g << 16) // g = 00000000 10010110 00000000 00000000
// Passo 3
r | g // t = 01100100 10010110 00000000 00000000
// Passo 4
(b << 8) // b = 00000000 00000000 11001000 00000000
// Passo 5
t | b // t = 01100100 10010110 11001000 00000000
// Passo 6
t | a // t = 01100100 10010110 11001000 11111111
// Passo 7
this.value = t
Esses passos é um mapa mental para entender como é feita a atribuição da variável value
com os quatro componentes informados pelo construtor. Agora vamos criar os métodos para extrair os componentes a partir da variável value
.
Para recuperar o componente RED deslocamos 24 bits à direita e preenchemos as lacunas com zero ignorando o sinal.
public int red() {
return this.value >>> 24;
}
Para recuperar os componentes GREEN e BLUE além de deslocar os bits para a direita, precisamos também “limpar” os componentes da esquerda, para isso utilizamos o operador &
com o segundo argumento 0xFF = 255 = 11111111, com isso somente os oito primeiros bits mais a direita serão conservados, todos os outros irão conter o valor zero.
public int green() {
return (this.value >>> 16) & 0xFF;
}
public int blue() {
return (this.value >>> 8) & 0xFF;
}
Por fim, para obter o componente ALPHA precisamos somente “limpar” os bits da esquerda, preservando somente os oito mais à direita.
public int alpha() {
return this.value & 0xFF;
}
A classe completa é exibida abaixo:
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, 0x255);
}
}
Conclusão
Conhecer os operadores bit à bit de sua linguagem preferida é parte fundamental no desenvolvimento de software, eles são úteis em diversas situações onde manipular os bits de um determinado valor é mais prático ou performático, ou simplesmente é a única ferramenta possível para resolver um determinado problema.
Não deixe de comentar sobre suas impressões e se curtiu compartilhe com seus conhecidos e amigos, até a próxima!