缩小基元转换期间的Java编译器行为

时间:2013-06-25 13:30:53

标签: java compiler-construction casting primitive scjp

K.Sierra和B.Bates在他们的书“SCJP学习指南”中写道

以下是合法byte b = 27;,但仅仅是因为编译器自动将文字值缩小为一个字节。换句话说,编译器放入强制转换。前面的代码与以下代码相同: byte b = (byte) 27;

在我看来,这种解释是不正确的。 这两行代码是否相同?

实际上

byte b = 27;

只是一个常数。并且编译时缩小常量是此代码有效的唯一原因。所以不需要演员阵容。缩小编译器时,只需检查指定的值是否适合变量的类型。 specification说:

  

如果变量的类型是 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!!

这让我觉得扩大/缩小转换和转换是根本不同的。但在各种来源中,它们经常互换使用。它是否正确?如果隐式缩小转换,是否会在封面下进行转换?

有人能解释上述书中描述的情况下编译器的真实行为吗?

2 个答案:

答案 0 :(得分:5)

测试它很容易。将以下内容放在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的不同之处是正确的,但它们在语义上相同,因为所有标准编译器都足够智能,可以将线路优化为单个字节码。

答案 1 :(得分:2)

在开始之前,重要的是要注意在java中,所有纯数字文字都是int值。

关于允许的非转换常量的关键短语可以在变量的类型中表示。这只意味着变量类型的常量“在范围内”,所以:

  • for byte -128 to 127
  • for short -32768 to 32767
  • for char 0 to 65535

如果转换为变量类型,则“范围内”值不会“丢失信息”。这就解释了为什么允许范围内的常量。

对于范围之外的值,需要显式强制转换,因为如果执行强制转换,信息将会丢失。当缩小演员表完成时,变量类型范围之外的位被简单地屏蔽掉 - 这就是“丢失信息”在这些情况下的含义。

就好像一个常数的赋值就像:

byte b = nnn & 0xFF;

如果nnn在范围内,那么掩码不会改变值,所以没有丢失,所以没问题 - 编译好。

如果nnn超出范围,信息将会丢失,因此需要明确的演员才能确认损失。

如果你还记得所有的整数文字都是整数,那么规则实际上与那些适用于将int分配给更窄的变量类型的规则没有什么不同,除非编译器在它知道值“适合”时不允许强制转换,