案例一:
short s = 1;
s = s + 1;
上述代码能否通过编译,为什么?
案例二:
short s = 1;
s += 1;
上述代码能否通过编译,为什么?
以上两个问题是非常经典的一道Java面试题,面试中出现的频率在70%以上。答案可能大家都非常清楚了,案例一可以不能通过编译,案例二可以,那么这又是为什么呢?
答案要从运算符的优先级、运算机制、默认数据类型和类型转换的机制中来寻找。
首先,我们来分析“ s = s + 1; ”。在这个表达式中,涉及到了加法(+)和赋值(=)两种运算。加法运算的优先级高于赋值运算,因此会先编译加法运算。在这个过程中,加号左侧为 short 类型的变量 s,右侧为常量值 1,1的默认数据类型为 int 类型,这种情况下,s 会自动类型提升,隐式转换为 int 类型。这个过程结束之后,会继续对赋值运算进行编译。此时,等号左侧为 short 类型变量 s,等号右侧为 int 类型变量,要把 int 类型变量的值赋给 short 类型变量,由于 int 类型的数据宽长为 32 位,short 类型的数据长度为16位,而变量的值在编译期间是无法确定的,所以编译器会认为在这个赋值过程中会发生数据信息丢失,从而编译不能通过。
我们再来看一些情况:
byte a = 3; // 编译正确
byte b = 1000; // 编译出错 Type mismatch: cannot convert from int to byte
float c = .7; // 编译出错 Type mismatch: cannot convert from double to float
在这个例子中,我们会有这样的疑问,同样是由 int 类型常量赋给 byte 类型变量,为什么 a = 3 可以通过,而 b = 1000 却不能通过。这是因为,对于常量,编译器是可以确定其值是否在目标类型的数值范围之内,3在 byte 类型的范围之内,因此转换后不会有数据信息丢失,编译成功,而1000超出了 byte 的数值范围,转换会造成数据信息丢失,转换失败。而 float c = .7,.7为 double 类型的常量,double 类型的精度高于 float 类型,如果从 double 转换为 float 则会产生精度的损失,所以编译不通过。(注意:double d = .7; float f = .7f; 这里,d 大于 f。大家可以通过程序验证一下)。
接下来,我们再来看第二个案例。这个案例中,只涉及到一个复合赋值“ += ”。复合赋值 E1 op= E2 等价于简单赋值 E1 =(T)((E1)op(E2)),其中 T 是 E1 的类型,除非 E1 只被计算一次。也就是说,复合赋值表达式自动地将它们所执行的计算的结果转型为其左侧变量的类型。如果结果的类型与该变量的类型相同,那么这个转型不会造成任何影响。然而,如果结果的类型比该变量的类型要宽,那么复合赋值操作符将悄悄地执行一个窄化原始类型转换。所以,s += 1; 先编译加法运算,再编译强制类型转换,最后编译赋值运算,这样,运算符左右两侧数据类型一致,编译通过。
补充点小知识,我们通过对字节码文件进行反编译,得到其对应的 JVM 指令,从 JVM 指令是否一致,就可以判断出两种操作的实质是否一样。首先来看源码一:
public class Demo {
public static void main(String[] args) {
short s = 1;
s += 1;
}
}
运行 javap -c -verbose Demo.class,得到结果一如下:
Classfile /C:/Users/Administrator/Desktop/Demo.class
Last modified 7月20日; size 268 bytes
MD5 checksum 88b70724a8593e4946d538b0460c5bc1
Compiled from "Demo.java"
public class Demo
minor version: 0
major version: 54
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // Demo
super_class: #3 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #3.#12 // java/lang/Object."":()V
#2 = Class #13 // Demo
#3 = Class #14 // java/lang/Object
#4 = Utf8 #5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 main
#9 = Utf8 ([Ljava/lang/String;)V
#10 = Utf8 SourceFile
#11 = Utf8 Demo.java
#12 = NameAndType #4:#5 // "":()V
#13 = Utf8 Demo
#14 = Utf8 java/lang/Object
{
public Demo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_1
1: istore_1
2: iload_1
3: iconst_1
4: iadd
5: i2s
6: istore_1
7: return
LineNumberTable:
line 5: 0
line 6: 2
line 7: 7
}
SourceFile: "Demo.java"
再来看另一段源码二:
public class Demo {
public static void main(String[] args) {
short s = 1;
s = (short) (s + 1);
}
}
运行 javap -c -verbose Demo.class,得到结果二如下:
Classfile /C:/Users/Administrator/Desktop/Demo.class
Last modified 7月20日; size 288 bytes
MD5 checksum 1100ccec295a1315d82752f3af1e9300
Compiled from "Demo.java"
public class com.cml.springcloud.Demo
minor version: 0
major version: 54
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // com/cml/springcloud/Demo
super_class: #3 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #3.#12 // java/lang/Object."":()V
#2 = Class #13 // com/cml/springcloud/Demo
#3 = Class #14 // java/lang/Object
#4 = Utf8 #5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 main
#9 = Utf8 ([Ljava/lang/String;)V
#10 = Utf8 SourceFile
#11 = Utf8 Demo.java
#12 = NameAndType #4:#5 // "":()V
#13 = Utf8 com/cml/springcloud/Demo
#14 = Utf8 java/lang/Object
{
public com.cml.springcloud.Demo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_1
1: istore_1
2: iload_1
3: iconst_1
4: iadd
5: i2s
6: istore_1
7: return
LineNumberTable:
line 5: 0
line 6: 2
line 7: 7
}
SourceFile: "Demo.java"
从得到的结果,我们可以看出,其对应的 JVM 指令是一致的,由此可以判定这两种表达式是等价的。
视频教程参考:Java视频教程合集
如果此博文对您由帮助,就打赏一下吧。您的支持,是我坚持的动力。
标题