注:如未特别说明,Java语言规范 jls 均基于JDK8,使用环境是 eclipse4.5 + win10 + JDK 8
本篇的知识点,主要是涉及到 Java 中一些比较常见的默认窄化处理(Java编译器自动添加的),这里将从一个问题开始,据说这也是一道常见的笔试题/面试题:
笔者注:其实这其中会涉及到一些编译优化和底层的知识,限于知识面,本篇不涉及,如有需要,可自行搜索。
本文的目录结构如下:
关于开篇提出的问题,这里先直接给出结论:
short i = 1; i += 1;
可以正确编译运行是因为Java编译器自己添加了强制窄化处理,即对于任何的T a; X b; a += b;
等价于T a; X b; a = (T) (a + b);
Java编译器会默认做这个显式强制转换(尽管有时候会出现精度问题,例如 b 是 float 、 double 类型,强烈建议不要有这样的操作)。前面的i += 1
其实就等价于i = (int) (i + 1)
,即便将数字1换成是double类型的1.0D也是如此。short i = 1; i = i + 1;
编译不通过的原因就很明显了:无论是代码中,还是Java编译器,都没有做强制转换,int 类型直接赋给 short ,因此编译出错。
接下来讲详细分析为什么 short i = 1; i += 1; 可以正确编译而 short i = 1; i = i + 1; 则会编译失败。先列一下搜出来的一些令人眼前一亮(or 困惑)的代码[16](来源 Java语言规范 jls 和 stackoverflow ,详细链接见参考):
public static void main(String[] args) {
// 注:short ∈ [-32768, 32767]
{
/*
* 1、对于 +=, -=, *=, /=, Java编译器默认会添加强制类型转换,
* 即 T a; X b; a += b; 等价于 T a; X b; a = (T) (a + b);
*/
// 0是int类型的常量,且在short范围内,被Java编译器默认强制转换的
short i = 0;
i += 1; // 等价于 i = (short) (i + 1);
System.out.println("[xin01] i=" + i); // 输出结果: 1
i = (short) (i + 1);
System.out.println("[xin02] i=" + i); // 输出结果: 2
/*
* 下面这2行都会有编译报错提示:
* Exception in thread "main" java.lang.Error: Unresolved compilation problem:
* Type mismatch: cannot convert from int to short
* [注]错误: 不兼容的类型: 从int转换到short可能会有损失
* Eclipse 也会有提示: Type mismatch: cannot convert from int to short
*/
// i = i + 1;
// i = 32768;
i = 0;
i += 32768; // 等价于 i = (short) (i + 32768); 下同
System.out.println("[xin03] i=" + i); // 输出结果: -32768
i += -32768;
System.out.println("[xin04] i=" + i); // 输出结果: 0
i = 0;
long j = 32768;
i += j;
System.out.println("[xin05] i=" + i); // 输出结果: -32768
i = 0;
float f = 1.23F;
i += f;
System.out.println("[xin06] i=" + i); // (小数位截断)输出结果: 1
i = 0;
double d = 4.56D;
i += d;
System.out.println("[xin07] i=" + i); // (小数位截断)输出结果: 4
i = 10;
i *= 3.14D;
System.out.println("[xin08] i=" + i); // 输出结果: 31
i = 100;
i /= 2.5D;
System.out.println("[xin09] i=" + i); // 输出结果: 40
}
{
/*
* 2、常量表达式和编译器优化: 常量折叠
*/
// 2 * 16383 = 32766
// (-2) * 16384 = -32768
// 都在 short 范围内,常量表达式在编译优化后直接用对应的常量结果,然后编译器做强制转换
short i = 2 * 16383; // 等价于 short i = (short) (2 * 16383);
short j = (-2) * 16384;
// 2 * 16384 = 32768,超过 short 范围,编译器不会做转换
// Type mismatch: cannot convert from int to short
// short k = 2 * 16384;
// 常量表达式在编译优化后直接用对应的常量结果,然后编译器做强制转换
short cThirty = 3 * 10;
short three = 3;
short ten = 10;
// Type mismatch: cannot convert from int to short
// short thirty = three * ten;
final short fTthree = 3;
final short fTen = 10;
// 常量表达式在编译优化后直接用对应的常量结果,然后编译器做强制转换
short fThirty = fTthree * fTen;
final short a = 16384;
final short b = 16383;
// 常量表达式在编译优化后直接用对应的常量结果,然后编译器做强制转换
short c = a + b;
}
}
接下来根据代码罗列的两部分分别进行说明:
这个结论其实来自于 Java语言规范 jls 对复合赋值运算符(Compound Assignment Operators)的相关描述,具体参考[1]15.26.2. Compound Assignment Operators https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.26.2,并且还举了相关的例子,如下:
A compound assignment expression of the form E1 op= E2
is equivalent to E1 = (T) ((E1) op (E2))
, where T
is the type of E1
, except that E1
is evaluated only once.
For example, the following code is correct:
short x = 3;
x += 4.6;
and results in x having the value 7 because it is equivalent to:
short x = 3;
x = (short)(x + 4.6);
stackoverflow 上的相关讨论可以参考[2]Why don‘t Java‘s +=, -=, *=, /= compound assignment operators require casting? https://stackoverflow.com/questions/8710619/why-dont-javas-compound-assignment-operators-require-casting
笔者注:
知道了Java语言相关的规范约定,我们就可以看出,与之对应的是以下这种出现编译错误的写法(报错提示:Type mismatch: cannot convert from int to short
):
short i = 1;
// i + 1 是 int 类型,需要强制向下类型转换
i = i + 1;
需要注意的是,前面的示例short x = 3;
中的3其实默认是 int 类型,但是却可以赋值给short类型的x。这里涉及到到的其实是 常量表达式。
Java语言规范[3]5.2. Assignment Conversion https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.2中提到:
In addition, if the expression is a constant expression (§15.28) of type byte, short, char, or int:
对于常量表达式,其结果是可以自动做窄化处理的,只要是在对应的数据类型范围内,Java编译器就进行做默认强制类型转换。
关于常量表达式,Java语言规范中[4]15.2. Forms of Expressions https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.2 中提到了有一类可以在编译期就确定值的表达式:常量表达式
Some expressions have a value that can be determined at compile time. These are constant expressions (§15.28).
具体说明参考[5]15.28. Constant Expressions https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.28, 如下都是相关的常量表达式例子:
true
(short)(1*2*3*4*5*6)
Integer.MAX_VALUE / 2
2.0 * Math.PI
"The integer " + Long.MAX_VALUE + " is mighty big."
另外还有关于final变量的说明[7]4.12.4. final Variables https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12.4
A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28).
通过常量表达式赋值的final变量都是常量,这种也是编译期可以确认最终值,会通过编译优化直接赋予最终值,而且可以直接依靠编译器做窄化处理。
相关讨论参考 [8]Java char to byte casting https://stackoverflow.com/questions/30346587/java-char-to-byte-casting
对于这一块,其实对应的是一个比较基础的编译器优化:常量折叠(Constant Folding),有兴趣的可以自行搜索。stackoverflow 上由相关的讨论,参考[9]、[10]、[11]
笔者注:
Java基础(001):关于 short i = 1; i += 1;
原文:https://www.cnblogs.com/wpbxin/p/14618755.html