Qual é o significado de double til (~~) em Java?

192

Ao navegar no código fonte do Guava, me deparei com o seguinte trecho de código (parte da implementação de hashCodepara a classe interna CartesianSet):

int adjust = size() - 1;
for (int i = 0; i < axes.size(); i++) {
    adjust *= 31;
    adjust = ~~adjust;
    // in GWT, we have to deal with integer overflow carefully
}
int hash = 1;
for (Set<E> axis : axes) {
    hash = 31 * hash + (size() / axis.size() * axis.hashCode());

    hash = ~~hash;
}
hash += adjust;
return ~~hash;

Ambos adjuste hashsão ints. Pelo que sei sobre Java, ~significa negação bit a bit, portanto, adjust = ~~adjuste hash = ~~hashdeve deixar as variáveis ​​inalteradas. Executando o pequeno teste (com afirmações ativadas, é claro),

for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
    assert i == ~~i;
}

confirma isso. Supondo que os caras da Goiaba saibam o que estão fazendo, deve haver uma razão para eles fazerem isso. A questão é o que?

EDITAR Como apontado nos comentários, o teste acima não inclui o caso em que ié igual Integer.MAX_VALUE. Como i <= Integer.MAX_VALUEsempre é verdade, precisaremos verificar esse caso fora do loop para impedir que ele faça loop para sempre. No entanto, a linha

assert Integer.MAX_VALUE == ~~Integer.MAX_VALUE;

gera o aviso do compilador "Comparando expressões idênticas", que praticamente o define.

Halle Knast
fonte
42
@dr_andonuts A goiaba é uma biblioteca bastante padrão para incluir em um projeto hoje em dia - acho que o conselho para fugir muito é equivocado.
usar o seguinte comando
4
A declaração não verifica a caixa de borda Integer.MAX_VALUE. Contraste com -(-Integer.MIN_VALUE) != Integer.MIN_VALUE.
Franky
3
@Franky O que maaartinus disse. -Integer.MIN_VALUEenvolve Integer.MIN_VALUE, negando que novamente simplesmente produz Integer.MIN_VALUEnovamente.
2
@maaartinus, @hvd, obrigado por apontar isso. Agora eu lembro disso -x = (~x) + 1.
quer
7
@dr_andonuts Trolling? Por que fugir de coisas que você não entende. É para isso que o StackOverflow está aqui: para ajudá-lo a aprender.
amigos estão

Respostas:

245

Em Java, isso não significa nada.

Mas esse comentário diz que a linha é especificamente para GWT, que é uma maneira de compilar Java para JavaScript.

Em JavaScript, números inteiros são como dobras que atuam como números inteiros. Eles têm um valor máximo de 2 ^ 53, por exemplo. Mas os operadores bit a bit tratam os números como se fossem 32 bits, exatamente o que você deseja neste código. Em outras palavras, ~~hashdiz "tratar hashcomo um número de 32 bits" em JavaScript. Especificamente, ele descarta todos, exceto os 32 bits inferiores (já que os ~operadores bit a bit olham apenas para os 32 bits inferiores), o que é idêntico ao funcionamento do estouro de Java.

Se você não tivesse isso, o código de hash do objeto seria diferente, dependendo de ser avaliado em Java-land ou JavaScript (por meio de uma compilação GWT).

yshavit
fonte
10
@harold Não é uma coisa do GWT, é uma coisa do JavaScript. Agora, os números funcionam nesse idioma.
usar o seguinte comando
18
@yshavit No entanto, é não como os números trabalhar em Java. Se o GWT não ocultar o fato de que os números são implementados de maneira diferente em JS e na JVM do usuário, será um compilador ruim.
Valderman
8
@harold, sim, é o JavaScript que implementa números inteiros incorretamente (na verdade não existe um tipo inteiro no JavaScript).
MikeTheLiar
8
@valderman Esse é um bom ponto. Adicionar o |0ou ~~parece que não seria difícil, embora eu não saiba qual seria o desempenho (você teria que adicioná-lo a cada passo de cada expressão). Não sei quais eram as considerações de design. Fwiw, a inconsistência está documentada na página de compatibilidade do GWT .
usar o seguinte comando
6
hashCodeé estranho, pois deliberadamente corteja, ou até espera, um transbordamento. O único lugar em que você pode observar inconsistência é onde um Java int normal seria excedido, o que não é um problema que aparece na maioria dos códigos; é apenas relevante neste caso estranho.
Louis Wasserman