Статьи

Несовместимая работа расширяет правила в Java

обзор

Когда вы выполняете унарную или двоичную операцию в Java, стандартным поведением является использование самого широкого операнда (или более широкого операнда для byte , short char и char ) Это легко понять, но может сбить с толку, если вы подумаете о том, каким может быть оптимальный тип.

умножение

Когда вы выполняете умножение, вы часто получаете намного большее число, чем любое из отдельных чисел по величине. то есть | a * b | >> | а | и | a * b | >> | б | часто бывает. И для маленьких типов это работает как ожидалось.

Рассмотрим эту программу:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;
}

который печатает:

1
2
3
4
5
int: 16129
int: 1073676289
int: -131071
int: 1
long: 1

Только byte * byte и short * short не переполняются, поскольку они были расширены. char * char не является значимой операцией, даже если она разрешена. Но int * int переполняется, хотя у нас есть длинный тип, который может хранить это значение без переполнения. И byte и short расширяются неявно, но не int . long действительно должен быть расширен, но у нас нет более широкого типа примитивов, который имел бы смысл когда-то, однако 64-битный примитив не кажется таким длинным в наши дни.

разделение

Деление немного странно в том смысле, что делитель может расширить результат. Наличие более широкого делителя, чем у числителя, не означает, что результат будет больше (но обычно он меньше)

1
2
3
4
5
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));

печать

1
2
3
4
5
int: 127
int: 127
int: 127
int: 127
long: 127

Когда вы делите byte/byte вы получаете int даже если вы не можете получить значение больше byte . (если вы не разделите Byte.MIN_VALUE на -1, в этом случае будет иметь место short ), и если вы разделите byte/long вы получите long даже если значение по-прежнему не может быть больше byte .

модуль

Когда вы выполняете модуль a % b , результат не может быть больше, чем b . И все же модуль будет шире, а не уменьшит результат.

01
02
03
04
05
06
07
08
09
10
11
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));

печать

01
02
03
04
05
06
07
08
09
10
int: 0
int: 127
int: 127
int: 127
long: 127
int: 1
int: 1
int: 1
int: 1
long: 1

Если вы модулируете X числом, результат не может быть больше / больше, чем X , он может быть только меньше. Тем не менее, JLS say он должен стать шире. Если вы модулируете X byte , результат может быть только в диапазоне byte .

Я также упомянул унарные операции, и, возможно, самый простой — унарный минус.

1
2
3
4
5
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));

печать

1
2
3
4
5
int: 128
int: 32768
int: 0
int: -2147483648
long: -9223372036854775808

В первых трех случаях тип расширен. byte может быть расширен до short , но это правильно как int . Однако для int и long он не расширен, и вы можете получить редкое переполнение.

Немного шансов — унарный плюс, который не меняет значение (и, следовательно, не может изменить его диапазон), но может расширить значение.

1
2
3
4
5
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));

печать

1
2
3
4
5
int: -128
int: -32768
int: 0
int: -2147483648
long: -9223372036854775808

Можем ли мы это исправить?

К сожалению нет. Слишком много кода зависит от этой логики. Например, скажем, вы пишете что-то вроде этого.

1
2
3
long i = ...
byte b = ...
long l = i % b + Integer.MAX_VALUE;

Если бы я% b превратился из long в byte , это выражение могло бы переполниться.

Вывод

Java может расширять некоторые значения, когда это необходимо, но также не может расширять некоторые int операции, которые действительно должны быть long . Это никогда не даст более узкого результата, даже если это может быть более логичным.

Нам нужно знать о крайних случаях, в частности int * int , и знать, как расширять их самостоятельно, когда мы видим такую ​​операцию. например

1
long l = (long) a * b;

Если мы не уверены, что a * b будет соответствовать значению int .

Ссылка: Непоследовательная работа расширяет правила в Java от нашего партнера JCG Питера Лоури из блога Vanilla Java .