Когда вы выполняете унарную или двоичную операцию в Java, стандартным поведением является использование самого широкого операнда (или более широкого операнда для байта, короткого и символьного). Это легко понять, но может сбить с толку, если вы подумаете о том, каким может быть оптимальный тип.
умножение
Когда вы выполняете умножение, вы часто получаете намного большее число, чем любое из отдельных чисел по величине. то есть | a * b | >> | а | и | a * b | >> | б | часто бывает. И для небольших типов это работает как ожидалось.
Рассмотрим эту программу
public static void main(String[] args) throws IOException { System.out.println(is(Byte.MAX_VALUE * Byte.MAX_VALUE)); System.out.println(is(Short.MAX_VALUE * Short.MAX_VALUE)); System.out.println(is(Character.MAX_VALUE * Character.MAX_VALUE)); System.out.println(is(Integer.MAX_VALUE * Integer.MAX_VALUE)); System.out.println(is(Long.MAX_VALUE * Long.MAX_VALUE)); } static String is(byte b) { return "byte: " + b; } static String is(char ch) { return "char: " + ch; } static String is(short i) { return "short: " + i; } static String is(int i) { return "int: " + i; } static String is(long l) { return "long: " + l; }
какие отпечатки
int: 16129 int: 1073676289 int: -131071 int: 1 long: 1
Только байты * байты и короткие * короткие не переполняются, поскольку они были расширены. char * char не является значимой операцией, даже если она разрешена. Но int * int переполняется, хотя у нас есть длинный тип, который может хранить это значение без переполнения. И байты и короткие расширяются неявно, но не int. longshould действительно расширить, но у нас нет более широкого примитивного типа, который имел бы смысл когда-то, однако 64-битный примитив не кажется таким длинным в наши дни.
разделение
Деление немного странно в том смысле, что делитель может расширить результат. Наличие более широкого делителя, чем у числителя, не означает, что результат будет больше (но обычно он меньше)
System.out.println(is(Byte.MAX_VALUE / (byte) 1)); System.out.println(is(Byte.MAX_VALUE / (short) 1)); System.out.println(is(Byte.MAX_VALUE / (char) 1)); System.out.println(is(Byte.MAX_VALUE / (int) 1)); System.out.println(is(Byte.MAX_VALUE/ (long) 1));
печать
int: 127 int: 127 int: 127 int: 127 long: 127
Когда вы делите байт / байт, вы получаете целое число, даже если вы не можете получить значение больше байта. (если вы не разделите Byte.MIN_VALUE на -1, в этом случае будет иметь место короткое замыкание), и если вы разделите байт / длинное, вы получите длинное, даже если значение по-прежнему не может быть больше байта.
модуль
Когда вы выполняете модуль a% b, результат не может быть больше, чем b. И все же модуль будет шире, а не уменьшит результат.
System.out.println(is(Byte.MAX_VALUE % Byte.MAX_VALUE)); System.out.println(is(Byte.MAX_VALUE % Short.MAX_VALUE)); System.out.println(is(Byte.MAX_VALUE % Character.MAX_VALUE)); System.out.println(is(Byte.MAX_VALUE % Integer.MAX_VALUE)); System.out.println(is(Byte.MAX_VALUE % Long.MAX_VALUE)); System.out.println(is(Byte.MAX_VALUE % (byte) 2)); System.out.println(is(Short.MAX_VALUE % (byte) 2)); System.out.println(is(Character.MAX_VALUE % (byte) 2)); System.out.println(is(Integer.MAX_VALUE % (byte) 2)); System.out.println(is(Long.MAX_VALUE % (byte) 2));
int: 0 int: 127 int: 127 int: 127 long: 127 int: 1 int: 1 int: 1 int: 1 long: 1
Если вы модулируете X числом, результат не может быть больше / больше, чем X, он может быть только меньше. Тем не менее, JLS говорят, что должно стать шире. Если вы модулируете X байтом, результат может быть только в диапазоне байта.
Отрицание
Я также упомянул унарные операции, и, возможно, самый простой — унарный минус.
System.out.println(is(-Byte.MIN_VALUE)); System.out.println(is(-Short.MIN_VALUE)); System.out.println(is(-Character.MIN_VALUE)); System.out.println(is(-Integer.MIN_VALUE)); System.out.println(is(-Long.MIN_VALUE));
печать
int: 128 int: 32768 int: 0 int: -2147483648 long: -9223372036854775808
В первых трех случаях тип расширен. Байт может быть расширен до короткого, но это правильно как int. Однако для int и long он не расширен, и вы можете получить редкое переполнение.
Немного шансов — унарный плюс, который не меняет значение (и, следовательно, не может изменить его диапазон), но может расширить значение.
System.out.println(is(+Byte.MIN_VALUE)); System.out.println(is(+Short.MIN_VALUE)); System.out.println(is(+Character.MIN_VALUE)); System.out.println(is(+Integer.MIN_VALUE)); System.out.println(is(+Long.MIN_VALUE));
печать
int: -128 int: -32768 int: 0 int: -2147483648 long: -9223372036854775808
Можем ли мы это исправить?
К сожалению нет. Слишком много кода зависит от этой логики. Например, скажем, вы пишете что-то вроде этого
long i = ... byte b = ... long l = i % b + Integer.MAX_VALUE;
Если бы я% b превратился из длинного в байт, это выражение могло бы переполниться.
Вывод
Java может расширять некоторые значения, когда это необходимо, но также не может расширять некоторые int-операции, которые действительно должны быть длинными. Это никогда не даст более узкого результата, даже если это может быть более логичным.
Нам нужно знать о крайних случаях, в частности int * int, и знать, как расширять их самостоятельно, когда мы видим такую операцию. например
длинный l = (длинный) a * b;
Если мы не уверены, что a * b будет соответствовать значению int.