K.Sierra 和 B.Bates 在他们的书《SCJP 学习指南》中写道
“以下内容是合法的
byte b = 27;
,但只是因为编译器自动将文字值缩小为字节。换句话说,编译器进行了强制转换。前面的代码与以下代码相同:byte b = (byte) 27;
”
我认为这个解释是不正确的。 这两行代码相同吗?
事实上
byte b = 27;
只是一个常数。常量的编译时缩小是此代码有效的唯一原因。所以不需要演员阵容。当缩小范围时,编译器只是检查指定的值是否适合变量的类型。 规范说:
如果变量的类型是 byte、short 或 char,并且常量表达式的值可以用变量的类型表示,则可以使用缩小原语转换。
第二种情况
byte b = (byte) 27;
转换确实在运行时发生,并且原始值是根据特定规则计算的。编译器不关心基本类型的兼容性。例如
byte b = 5.0; // compile error
byte b = 277777777; // compile error
byte b = (byte) 5.0; // valid!
byte b = (byte) 277777777; // valid!!
这让我觉得加宽/缩小转换和转换是根本不同的。但在各种来源中,它们经常互换使用。这是正确的吗?在隐式缩小转换的情况下,转换是否会在幕后进行?
任何人都可以解释编译器在上述书中描述的情况下的真实行为吗?
测试起来很容易。将以下内容放入 Temp.java 中:
class Temp {
public static void main(String[] argv) {
byte b = 27;
System.out.println(b);
}
}
现在用你最喜欢的编译器编译它:
$ javac Temp.java
现在用 javap 转储字节码:
$ javap -c Temp.class
Compiled from "Temp.java"
class Temp {
Temp();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 27
2: istore_1
3: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
6: iload_1
7: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
10: return
}
现在将
27
替换为 (byte)27
并再次运行。你会发现没有什么区别。事实上,两个类文件将具有相同的 md5sum。
字节码中没有运行时转换,因为编译器发现不需要它,并将其优化掉。
我相信你是正确的,byte b = 27
行与
byte b = (byte) 27
行不同,但它们在语义上相同,因为所有标准编译器都足够聪明,可以将行优化为单个字节码。
int
值。关于允许的未转换常量的关键短语是
可以用变量的类型表示。这仅仅意味着常量“在变量类型的范围内”,所以:
byte
-128 至 127
short
-32768 至 32767
char
0 到 65535
对于超出范围的值,需要显式转换,因为如果执行转换,信息将会丢失。当完成缩小转换时,变量类型范围之外的位将被简单地屏蔽掉 - 这就是在这些情况下“丢失信息”的含义。
常量的赋值就像这样:
byte b = nnn & 0xFF;
如果 nnn 在范围内,则掩码不会更改值,因此不会丢失任何内容,因此没有问题 - 编译正常。
如果 nnn 超出范围,信息将会丢失,因此需要显式转换来确认丢失。
如果您还记得所有整数文字都是 int,那么这些规则实际上与将 int 分配给较窄变量类型的规则没有什么不同,除非编译器知道该值将“适合”时不允许进行强制转换,