Есть две взаимосвязанных причины такого поведения. Во-первых,
спецификация языка, стандартизирующая факт того, что перемножение int'ов даёт int:
When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:
- If any operand is of a reference type, it is subjected to unboxing conversion (§5.1.8).
- Widening primitive conversion (§5.1.2) is applied to convert either or both operands as specified by the following rules:
- If either operand is of type double, the other is converted to double.
- Otherwise, if either operand is of type float, the other is converted to float.
- Otherwise, if either operand is of type long, the other is converted to long.
- Otherwise, both operands are converted to type int.
After the conversion(s), if any, value set conversion (§5.1.13) is then applied to each operand.
Binary numeric promotion is performed on the operands of certain operators:
- The multiplicative operators *, /, and % (§15.17)
- ...
Во-вторых, принципы работы стековых виртуальных машин. Вот так выглядит байткод метода main:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=6, args_size=1
0: ldc #2 // int 300000
2: istore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: ldc #4 // String Input days:
8: invokevirtual #5 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
11: new #6 // class java/util/Scanner
14: dup
15: getstatic #7 // Field java/lang/System.in:Ljava/io/InputStream;
18: invokespecial #8 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V
21: astore 5
23: aload 5
25: invokevirtual #9 // Method java/util/Scanner.nextInt:()I
28: istore 4
30: iload 4
32: ldc #10 // int 86400
34: imul
35: iload_1
36: imul
37: i2l
38: lstore_2
39: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
42: new #11 // class java/lang/StringBuilder
45: dup
46: invokespecial #12 // Method java/lang/StringBuilder."<init>":()V
49: ldc #13 // String Light distance:
51: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
54: lload_2
55: invokevirtual #15 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
58: ldc #16 // String km.
60: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
63: invokevirtual #17 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
66: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
69: return
LocalVariableTable:
Start Length Slot Name Signature
0 70 0 args [Ljava/lang/String;
3 67 1 lightSpeed I
39 31 2 calculatedDistance J
30 40 4 runningTime I
23 47 5 input Ljava/util/Scanner;
Интересны смещения с 30-го по 38-е. В стек загружаются два целых числа, перемножаются операцией
imul
, в результате которой с вершины стека убираются исходные два числа и помещается результат их перемножения, загружается ещё одно, ещё раз перемножаются, потом значение на вершине стека приводится к
long
операцией
i2l
и сохраняется в переменную
calculatedDistance
. Естественно, если результат работы
imul
окажется слишком большим, то произойдёт переполнение.
Исправить это легко, достаточно изменить тип константы:
calculatedDistance = runningTime * 86400L * lightSpeed;
Тогда компилятор выберет другие опкоды, оперирующие long'ами:
30: iload 4
32: i2l
33: ldc2_w #10 // long 86400l
36: lmul
37: iload_1
38: i2l
39: lmul
40: lstore_2