为什么“short thirty = 3 * 10”是一个合法的赋值语句?

103
如果在算术运算中,short 被自动提升为 int,那么为什么以下代码会出现错误:

short thirty = 10 * 3;

将变量thirty合法地赋值为short类型?

进而,这样做:

short ten = 10;
short three = 3;
short thirty = ten * three; // DOES NOT COMPILE AS EXPECTED

还有这个:

int ten = 10;
int three = 3;
short thirty = ten * three; // DOES NOT COMPILE AS EXPECTED

此代码编译不通过,因为将一个int值赋给short类型需要强制类型转换。

数字文字有什么特别之处吗?


23
编译器很可能会将short thirty = 10 * 3;替换为short thirty = 30;,这是一条有效的语句。具体可以参考Java语言规范的相关章节。(我需要查找相关章节才能确定)。 - Thomas
编译器计算 10 * 3 并用结果初始化变量。在您的错误示例中,计算发生在运行时,JVM强制转换short类型。 - Felix
我认为这是一个重复的问题,与https://dev59.com/MF0a5IYBdhLWcg3wboIv或http://stackoverflow.com/questions/9379983/type-casting-of-byte-and-int相同。但是请注意,`final int ten = 10; final int three = 3; short thirty = ten * three;`可以编译通过。 - Marco13
7
如果在算术运算中,短整型会自动转换为整型--这不相关。数字103都不是短整型,也没有被提升为整型,它们是字面常量。 - Matthew Read
@MatthewRead:但即使作为字面值,它们也必须以特定的数据类型进行评估,对吧?因此,103被编译器评估为 int,这是正确的吗? - LarsH
@LarsH - 并非总是如此。short s = 5int i =10 分别被评估为 shortint。问题出现在我们有一个 悬空常量 的情况下。如果 short s = 5;,那么 short s2 = s+5 就不起作用,因为编译器认为 s+5 会导致一个 int。如果 sfinal(编译时常量),那么编译器知道 s+5 可以适合一个 short,因此它将被允许。 - TheLostMind
3个回答

138

因为编译器在编译时将10*3替换为30。所以实际上:short thirty = 10 * 3在编译时计算。

尝试将tenthree更改为final short(使它们成为编译时常量),看看会发生什么:P

使用javap -v检查字节码,针对两个版本(10*3final short)。您将能够看到几乎没有区别。

好的,所以这里是不同情况下的字节码差异。

情况 -1 :

 

Java代码:     main(){       short s = 10 * 3;     }

字节码:

stack=1, locals=2, args_size=1
         0: bipush        30  // directly push 30 into "s"
         2: istore_1      
         3: return   

案例-2:

public static void main(String arf[])  {
   final short s1= 10;
   final short s2 = 3;
   short s = s1*s2;
}

字节码:

  stack=1, locals=4, args_size=1
         0: bipush        10
         2: istore_1      
         3: iconst_3      
         4: istore_2      
         5: bipush        30 // AGAIN, push 30 directly into "s"
         7: istore_3      
         8: return   

第三种情况:

public static void main(String arf[]) throws Exception {
     short s1= 10;
     short s2 = 3;
     int s = s1*s2;
}

字节码:

stack=2, locals=4, args_size=1
         0: bipush        10  // push constant 10
         2: istore_1      
         3: iconst_3        // use constant 3 
         4: istore_2      
         5: iload_1       
         6: iload_2       
         7: imul          
         8: istore_3      
         9: return 
在上面的例子中,103 取自本地变量 s1s2

17
喜欢“尝试将十和三更改为final short”的练习 :) - S. Pauk
1
@SergeyPauk - 这对于理解编译时常量真的非常重要..适用于所有原始类型(包括字符串..) :) - TheLostMind
1
@TheLostMind 我建议更好的措辞是“你会发现在反编译的代码中这两行代码没有区别”,因为这不就是你的观点吗? - S. Pauk
4
有趣的是,在 switch 结构中,case 10*3: 这样的写法也是合法的。 - Ceiling Gecko
5
同样地,在枚举结构中也是如此。事实上,对于位域枚举常量使用类似 1 << 5 的方法是惯用的。 - Bathsheba
1
@CeilingGecko - 是的,这是合法的。当i被标记为final short时,case i : 将起作用。 - TheLostMind

18

确实有一些特殊情况需要注意: 10 * 3 会在编译时进行求值。因此,对于相乘的字面量,您不需要显式地进行(short)转换。

ten * three 无法在编译时求值,因此需要显式转换。

如果tenthree 标记为final,情况将有所不同。


2
以下答案添加了JLS部分和关于此行为的一些详细信息。
根据JLS §15.2 - 表达式形式

有些表达式具有编译时可以确定值的值。 这些是常量表达式(§15.28)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接