обзор
Когда вы выполняете унарную или двоичную операцию в 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 . |