缩小原语转换期间的Java编译器行为

缩小原语转换期间的Java编译器行为,java,compiler-construction,casting,primitive,scjp,Java,Compiler Construction,Casting,Primitive,Scjp,K.Sierra和B.Bates在他们的书《SCJP学习指南》中写道 以下是合法的字节b=27;,但这仅仅是因为编译器自动将文本值缩小为一个字节。换句话说,编译器放入强制转换。前面的代码与下面的代码相同:字节b=(字节)27; 我认为这个解释是不正确的这两行代码相同吗? 其实 byte b = 27; 它只是一个常数。编译时常数的缩小是该代码有效的唯一原因。所以不需要石膏。缩小范围时,编译器只检查指定的值是否适合变量的类型。 报告说: 如果变量的类型是byte、short或char,并且常量表

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

以下是合法的
字节b=27;
,但这仅仅是因为编译器自动将文本值缩小为一个字节。换句话说,编译器放入强制转换。前面的代码与下面的代码相同:
字节b=(字节)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
  }
$javap-c临时类
从“Temp.java”编译而来
班级临时工{
温度();
代码:
0:aload_0
1:invokespecial#1//方法java/lang/Object。“:()V
4:返回
公共静态void main(java.lang.String[]);
代码:
0:bipush 27
2:istore_1
3:getstatic#2//Field java/lang/System.out:Ljava/io/PrintStream;
6:iload_1
7:invokevirtual#3//方法java/io/PrintStream.println:(I)V
10:返回
}
现在将
27
替换为
(字节)27
,然后再次运行。你会发现没有区别。事实上,这两个类文件将具有相同的md5sum

字节码中没有运行时强制转换,因为编译器认为不需要它,并对它进行了优化


我相信你是对的,行
字节b=27
在语法上不同于行
字节b=(字节)27
,但它们在语义上是相同的,因为所有标准编译器都足够聪明,可以将行优化为单个字节码

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

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

  • 对于
    字节
    -128到127
  • 对于
    short
    -32768到32767
  • 对于
    char
    0到65535
如果强制转换为变量类型,“范围内”的值不会“丢失信息”。这就解释了为什么允许使用范围内常量

对于范围之外的值,需要显式转换,因为如果执行转换,信息将丢失。当一个变窄转换完成时,变量类型范围之外的位被简单地屏蔽掉——这就是在这些情况下“丢失信息”的意思

就好像常数的赋值是这样的:

byte b = nnn & 0xFF;
如果nnn在范围内,则掩码不会更改值,因此不会丢失任何内容,因此没有问题-编译正常

如果nnn超出范围,信息将丢失,因此需要显式强制转换来确认丢失

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

可能重复的