Что означает двойная тильда (~~) в Java?

192

Просматривая исходный код Guava, я наткнулся на следующий фрагмент кода (часть реализации hashCodeдля внутреннего класса 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;

Оба adjustи hashявляются ints. Из того, что я знаю о Java, ~подразумевается побитовое отрицание, поэтому adjust = ~~adjustи hash = ~~hashследует оставить переменные без изменений. Запуск небольшого теста (с включенными утверждениями, конечно),

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

подтверждает это. Предполагая, что парни из гуавы знают, что они делают, у них должна быть причина для этого. Вопрос в чем?

РЕДАКТИРОВАТЬ Как указано в комментариях, тест выше не включает в себя случай, когда iравно Integer.MAX_VALUE. Так i <= Integer.MAX_VALUEкак всегда верно, нам нужно будет проверить этот случай вне цикла, чтобы предотвратить его зацикливание навсегда. Тем не менее, линия

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

выдает предупреждение компилятора «Сравнение идентичных выражений», которое в значительной степени его прибивает.

Галле Кнаст
источник
42
@dr_andonuts Guava - довольно стандартная библиотека для включения в проект в наши дни - я думаю, что совет убежать далеко не уместен.
ишавит
4
Утверждение не проверяет крайний случай Integer.MAX_VALUE. Контраст с -(-Integer.MIN_VALUE) != Integer.MIN_VALUE.
Фрэнки
3
@Franky То, что сказал maaartinus. -Integer.MIN_VALUEоборачивается Integer.MIN_VALUE, так отрицая, что снова просто производит Integer.MIN_VALUEснова.
2
@maaartinus, @hvd, спасибо, что указал на это. Теперь я это помню -x = (~x) + 1.
Фрэнки
7
@dr_andonuts Троллинг? Зачем убегать от вещей, которые вы не понимаете. Вот для чего предназначен StackOverflow: чтобы помочь вам учиться.
Джаред Берроуз

Ответы:

245

На Java это ничего не значит.

Но этот комментарий говорит, что эта строка специально для GWT, который является способом компиляции Java в JavaScript.

В JavaScript целые числа вроде как удваивают то, что действуют как целые числа. Например, они имеют максимальное значение 2 ^ 53. Но побитовые операторы обрабатывают числа, как если бы они были 32-битными, а это именно то, что вам нужно в этом коде. Другими словами, в JavaScript ~~hashнаписано «рассматривать hashкак 32-разрядное число». В частности, он отбрасывает все, кроме младших 32 бит (так как побитовое~ операторы смотрят только на нижние 32 бита), что идентично тому, как работает переполнение Java.

Если у вас его нет, хеш-код объекта будет отличаться в зависимости от того, будет ли он оценен на Java-земле или на JavaScript-земле (посредством компиляции GWT).

yshavit
источник
10
@harold Это не GWT, это JavaScript. Вот только сейчас цифры работают на этом языке.
ишавит
18
@yshavit Тем не менее, это не так, как числа работают в Java. Если GWT не скрывает тот факт, что числа реализованы в JS и JVM по-разному от пользователя, то это действительно плохой компилятор.
Вальдерман
8
@harold, да, это JavaScript, который неправильно реализует целые числа (в JavaScript нет такого понятия как целочисленный тип).
MikeTheLiar
8
@valderman Это хороший момент. Добавление |0или ~~звучит так, как будто это не сложно, хотя я не знаю, каким будет удар по производительности (его нужно добавлять на каждом шаге каждого выражения). Я не знаю, каковы были соображения дизайна. Между прочим, несоответствие задокументировано на странице совместимости GWT .
ишавит
6
hashCodeстранно, что он намеренно вызывает или даже ожидает, что произойдет переполнение. Единственное место, где вы можете наблюдать несогласованность, - это переполнение нормального Java int, что не является проблемой, встречающейся в большинстве кода; это просто актуально в этом странном случае.
Луи Вассерман