Java中的盒子在使用等号运算符时会扩展。

4
Number n = 10; 
int i = 10;
System.out.println(n == i);

基于 "You CAN box then widen"。为什么上述代码会出现编译时错误?
我猜测如果 i 首先被装箱成 Integer,然后扩展成 Number,结果将始终为 false。
有关使用 == 运算符比较原语和对象时的任何规范吗?是否总是尝试执行拆箱,必要时进行扩展?

1
编译时错误是什么? - chrylis -cautiouslyoptimistic-
@jlordo。令人惊讶的是,在我的机器上无法运行。 - Rohit Jain
也许这取决于Java版本。您能否发布您使用的版本以及它是否可以编译? - Aaron Digulla
编译于1.7_0_21-b11 64位。 - BlackBox
@AaronDigulla 我使用的是JDK 1.7版本。 - L4zy
显示剩余3条评论
3个回答

9

根据JLS的规定, 一个装箱值和一个非装箱值之间的==比较会导致拆箱转换,而不是相反(否则你将使用引用相等性而不是值相等性)。编译器无法解包一个普通的NumberNumber本身不可“转换为数字类型”。

Java 7编译器似乎很聪明。这个版本和将比较移动到私有方法中的版本的汇编输出完全忽略了声明类型Number,一切正常运行。将该方法改为public,其行为就如规定的那样:转换不是列出的将发生拆箱的类型之一,编译器将10装箱为Integer并按引用比较,这意味着如果您尝试使用Integer.valueOf(10),则在范围-128..127内会得到true,如果使用其他任何东西(另一个宽度、new Integer(10)),则会得到false。
您的代码输出(请注意,Number不在任何地方出现,而在第18行基于引用相等进行比较;尝试使用L或转换为short):
public static void main(java.lang.String[]);
  Code:
   0: bipush        10
   2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   5: astore_1      
   6: bipush        10
   8: istore_2      
   9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
  12: aload_1       
  13: bipush        10
  15: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  18: if_acmpne     25
  21: iconst_1      
  22: goto          26
  25: iconst_0      
  26: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
  29: return

防止优化的版本:

public class Test
{
    public static void main(String[] args)
    {
        Number n = new Integer(10);
        compare(n);
    }

    public static void compare(Number n)
    {
        int i=10;
        System.out.println(n == 10);
    }
}

汇编语言;请注意,在第12行中仍然进行引用比较:

public static void main(java.lang.String[]);
  Code:
   0: new           #2                  // class java/lang/Integer
   3: dup           
   4: bipush        10
   6: invokespecial #3                  // Method java/lang/Integer."<init>":(I)V
   9: astore_1      
  10: aload_1       
  11: invokestatic  #4                  // Method compare:(Ljava/lang/Number;)V
  14: return        

public static void compare(java.lang.Number);
  Code:
   0: bipush        10
   2: istore_1      
   3: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   6: aload_0       
   7: bipush        10
   9: invokestatic  #6                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  12: if_acmpne     19
  15: iconst_1      
  16: goto          20
  19: iconst_0      
  20: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
  23: return

@chrylis,您能告诉我确切的位置,说明拆箱优先于装箱吗? - L4zy
@arshajii 感谢您的纠正,我选错了锚点。已经修复。 - chrylis -cautiouslyoptimistic-
@chrylis:我取消了我的踩,但是我没有看到第二个版本有什么不同。当我将方法设置为私有时,它并没有改变什么 :-/ - Aaron Digulla
编译器似乎以 DWIM 的方式不符合规范。请注意,规范在这里特别要求进行取消装箱转换,但编译器以一种装箱的方式对int进行了处理,这可能是程序员想要的。Eclipse 编译器是正确的。 - chrylis -cautiouslyoptimistic-
@arshajii 本质上是的,在“我应该是这样做”的方向上,但仍然是危险和错误的。我发布了实际的编译器输出,很明显它与规范不符。 - chrylis -cautiouslyoptimistic-
显示剩余9条评论

3
您的问题中的代码在使用Eclipse编译器的Java 6和7版本中给出了"Incompatible operand types Number and int"。然而,使用Oracle的Java 7 SDK中的javac编译器,则可以编译并打印true
为什么呢?
赋值语句Number n = 10将被转换为Number n = Integer.valueOf(10); 稍后,编译器将创建n == Integer.valueOf(10)(自动装箱整数值)。
这样做是因为Integer.valueOf()对于小整数数值保持内部缓存,并总是返回它们的相同实例。
Integer.valueOf(10) == Integer.valueOf(10)

但这只是实现的副作用,你不应该依赖它。

字节码:

  public static void main(java.lang.String[]);
    Code:
       0: bipush        10
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: astore_1      
       6: bipush        10
       8: istore_2      
       9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: aload_1       
      13: iload_2       
      14: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      17: if_acmpne     24
      20: iconst_1      
      21: goto          25
      24: iconst_0      
      25: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      28: return        

1

Oracle的JDK 7中的编译器允许您通过自动装箱原始类型来编译代码,但这违反了Java语言规范。

根据JLS第15.21节,有三种变体的等号运算符:

15.21.1. 数值等式运算符 == 和 !=

描述了"如果等式运算符的操作数都是数值类型,或者一个是数值类型而另一个可以转换为数值类型(§5.1.8),则该运算符返回 true"。在这种情况下,一个操作数具有数值类型,但另一个操作数不可转换为数值类型(拆箱)。

15.21.2. 布尔等式运算符 == 和 !=

描述了"如果等式运算符的操作数都是布尔类型,或者一个操作数是布尔类型而另一个是Boolean类型,则该运算符返回true"。这也不是这种情况。

15.21.3. 引用等式运算符 == 和 !=

描述了等号运算符“如果等号运算符的操作数都是引用类型或空类型,则为真”。但事实并非如此。
由于“==”运算符的操作数不匹配JLS定义的三种变体中的任何一种,因此代码不应编译。
Java 7编译器实际上所做的是通过自动装箱将原始操作数转换为引用类型并比较引用来使用引用相等运算符。由于这种变体的自动装箱(根据§5.1.7转换为引用类型)未记录在文档中,就像数值等式运算符指定的取消装箱(§5.1.8)一样,编译器应用了JLS未记录的功能。
在JDK 5和6中存在一个bug来处理此行为,据称已修复。在Java 7中,一些自动转换和装箱/拆箱情况被添加到JLS,我几乎认为有人打算为等号运算符添加自动装箱,并将该错误留在编译器中而没有实际更新JLS。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接