Почему int я = 1024 * 1024 * 1024 * 1024 компилируется без ошибок?
Предел int
составляет от -2147483648 до 2147483647.
Если я ввожу
int i = 2147483648;
тогда Eclipse предложит красное подчеркивание под "2147483648".
Но если я это сделаю:
int i = 1024 * 1024 * 1024 * 1024;
он будет компилироваться отлично.
public class Test {
public static void main(String[] args) {
int i = 2147483648; // error
int j = 1024 * 1024 * 1024 * 1024; // no error
}
}
Может быть, это основной вопрос в Java, но я понятия не имею, почему второй вариант не вызывает ошибок.
Ответы
Ответ 1
В этом утверждении нет ничего плохого; вы просто умножаете 4 числа и присваиваете их int, там просто происходит переполнение. Это отличается от назначения единственного литерала, который был бы проверен границами во время компиляции.
Это литерал вне пределов, который вызывает ошибку, а не назначение:
System.out.println(2147483648); // error
System.out.println(2147483647 + 1); // no error
В отличие от этого литерального текста long
будет компилироваться отлично:
System.out.println(2147483648L); // no error
Обратите внимание, что на самом деле результат все еще вычисляется во время компиляции, потому что 1024 * 1024 * 1024 * 1024
является постоянным выражением:
int i = 1024 * 1024 * 1024 * 1024;
становится:
0: iconst_0
1: istore_1
Обратите внимание, что результат (0
) просто загружается и сохраняется, и никакого умножения не происходит.
Из JLS §3.10.1 (спасибо @ChrisK за то, что он поднял его в комментариях):
Это ошибка времени компиляции, если десятичный литерал типа int
больше, чем 2147483648
(2 31), или если десятичный литерал 2147483648
появляется где угодно кроме как операнд унарного оператора минус (§15.15.4).
Ответ 2
1024 * 1024 * 1024 * 1024
и 2147483648
не имеют одинакового значения в Java.
На самом деле, 2147483648
НЕ ДАЕТ ЗНАЧЕНИЕ (хотя 2147483648L
is) в Java. Компилятор буквально не знает, что это такое, или как его использовать. Так он скулит.
1024
- допустимый int в Java, а действительный int
, умноженный на другой действительный int
, всегда является допустимым int
. Даже если это не то же значение, которое вы интуитивно ожидаете, потому что вычисление будет переполнено.
Пример
Рассмотрим следующий пример кода:
public static void main(String[] args) {
int a = 1024;
int b = a * a * a * a;
}
Ожидаете ли вы, что это приведет к ошибке компиляции? Сейчас становится немного более скользкой.
Что, если мы поставим цикл с 3 итерациями и умножим на цикл?
Компилятор разрешен для оптимизации, но при этом он не может изменить поведение программы.
Некоторая информация о том, как этот случай фактически обрабатывается:
В Java и многих других языках целые числа состоят из фиксированного числа бит. Вычисления, которые не соответствуют заданному числу бит, будут overflow; вычисление в основном выполняется модулем 2 ^ 32 в Java, после чего значение преобразуется обратно в целое число со знаком.
Другие языки или API используют динамическое число бит (BigInteger
в Java), создают исключение или задают значение магическим значением, таким как not-a-number.
Ответ 3
Я понятия не имею, почему второй вариант не вызывает ошибок.
Поведение, которое вы предлагаете, то есть создание диагностического сообщения, когда вычисление создает значение, большее, чем наибольшее значение, которое может быть сохранено в целочисленном размере, является функцией. Для использования какой-либо функции необходимо учитывать эту функцию, которая считается хорошей идеей, спроектирована, спроектирована, реализована, протестирована, документирована и отправлена пользователям.
Для Java одна или несколько вещей в этом списке не произошли, и поэтому у вас нет этой функции. Я не знаю, какой из них; вам придется спросить разработчика Java.
Для С# все это произошло - около четырнадцати лет назад - и поэтому соответствующая программа на С# вызвала ошибку с С# 1.0.
Ответ 4
В дополнение к ответу arshajii я хочу показать еще одну вещь:
Это не назначение, которое вызывает ошибку, а просто использование литерала.
При попытке
long i = 2147483648;
вы заметите, что это также вызывает ошибку компиляции, поскольку правая сторона все еще является int
-литером и вне диапазона.
Таким образом, операции с int
-значениями (и в том числе присваиваниями) могут переполняться без компиляции-ошибки (и без ошибки выполнения), но компилятор просто не может обрабатывать эти слишком большие литералы.
Ответ 5
A: Потому что это не ошибка.
Фон: Умножение 1024 * 1024 * 1024 * 1024
приведет к переполнению. Переполнение очень часто является ошибкой. Различные языки программирования создают другое поведение при переполнении. Например, C и С++ называют это "undefined поведение" для целых чисел со знаком, а поведение определяется беззнаковыми целыми числами (возьмите математический результат, добавьте UINT_MAX + 1
, пока результат отрицательный, вычтите UINT_MAX + 1
, пока результат больше, чем UINT_MAX
).
В случае Java, если результат операции с int
не находится в допустимом диапазоне, концептуально Java добавляет или вычитает 2 ^ 32, пока результат не окажется в допустимом диапазоне. Таким образом, выражение является полностью законным, а не ошибочным. Это просто не дает результата, на который вы, возможно, надеялись.
Можно с уверенностью утверждать, полезно ли это поведение, и должен ли компилятор дать вам предупреждение. Я бы сказал, что предупреждение было бы очень полезно, но ошибка была бы неправильной, поскольку это легальная Java.