Co oznacza podwójna tylda (~~) w Javie?

192

Przeglądając kod źródłowy Guava natrafiłem na następujący fragment kodu (część implementacji hashCodeklasy wewnętrznej 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;

Oba są adjusti hashint. Z tego, co wiem o Javie, ~oznacza bitową negację, więc adjust = ~~adjusti hash = ~~hashpowinienem pozostawić zmienne bez zmian. Uruchomienie małego testu (oczywiście z włączonymi asercjami),

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

potwierdza to. Zakładając, że Guava wiedzą, co robią, musi istnieć powód, aby to zrobić. Pytanie brzmi: co?

EDYCJA Jak wskazano w komentarzach, powyższy test nie obejmuje przypadku, w którym ijest równy Integer.MAX_VALUE. Ponieważ i <= Integer.MAX_VALUEzawsze jest to prawdą, musimy sprawdzić tę skrzynkę poza pętlą, aby zapobiec jej zapętleniu na zawsze. Jednak linia

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

wyświetla ostrzeżenie kompilatora „Porównywanie identycznych wyrażeń”, które w znacznym stopniu go dotyka.

Halle Knast
źródło
42
@dr_andonuts Guava to obecnie dość standardowa biblioteka do włączenia do projektu - myślę, że rada ucieczki jest niewłaściwa.
yshavit,
4
Aser nie sprawdza wielkości krawędzi Integer.MAX_VALUE. Porównaj z -(-Integer.MIN_VALUE) != Integer.MIN_VALUE.
Franky,
3
@Franky Co powiedział maaartinus. -Integer.MIN_VALUEowija się wokół Integer.MIN_VALUE, tak negując, że znów po prostu produkuje Integer.MIN_VALUEponownie.
2
@maaartinus, @hvd, dzięki za zwrócenie na to uwagi. Teraz to pamiętam -x = (~x) + 1.
Franky
7
@dr_andonuts Trolling? Po co uciekać od rzeczy, których nie rozumiesz. Do tego właśnie służy StackOverflow: aby pomóc Ci się uczyć.
Jared Burrows,

Odpowiedzi:

245

W Javie to nic nie znaczy.

Ale ten komentarz mówi, że wiersz jest specjalnie dla GWT, który jest sposobem na kompilację Java do JavaScript.

W JavaScript, liczby całkowite są jakby liczbami podwójnymi, które działają jak liczby całkowite. Na przykład mają maksymalną wartość 2 ^ 53. Ale operatory bitowe traktują liczby tak, jakby były 32-bitowe, co jest dokładnie tym, czego chcesz w tym kodzie. Innymi słowy, w JavaScript ~~hashmówi „traktuj hashjak liczbę 32-bitową”. W szczególności odrzuca wszystkie oprócz dolnych 32 bitów (ponieważ ~operatory bitowe spoglądają tylko na dolne 32 bity), co jest identyczne z tym, jak działa przepełnienie Javy.

Jeśli tego nie masz, kod skrótu obiektu byłby różny w zależności od tego, czy jest on oceniany w Javie, czy w JavaScript (za pomocą kompilacji GWT).

yshavit
źródło
10
@harold To nie jest GWT, to JavaScript. Właśnie teraz liczby działają w tym języku.
yshavit,
18
@yshavit Jednak nie tak działają liczby w Javie. Jeśli GWT nie ukrywa faktu, że liczby są zaimplementowane inaczej w JS i JVM przed użytkownikiem, to rzeczywiście jest to zły kompilator.
valderman
8
@harold, tak, to JavaScript nieprawidłowo implementuje liczby całkowite (w JavaScript nie ma czegoś takiego jak liczba całkowita).
MikeTheLiar,
8
@valderman To dobra uwaga. Dodanie |0lub ~~brzmi, jakby to nie było trudne, choć nie wiem, jaki byłby hit wydajności (trzeba by to dodawać na każdym etapie każdego wyrażenia). Nie wiem, jakie były względy projektowe. Fwiw, niespójność jest udokumentowana na stronie kompatybilności GWT .
yshavit,
6
hashCodejest dziwne, ponieważ celowo sądzi, a nawet spodziewa się, że nastąpi przepełnienie. Jedynym miejscem, w którym można zaobserwować niespójność, jest przepełnienie normalnej int Java, co nie występuje w większości kodu; ma to znaczenie tylko w tym dziwnym przypadku.
Louis Wasserman,