Number n = 10;
int i = 10;
System.out.println(n == i);
基于“你可以加箱然后加宽”。为什么上面的代码会产生编译时错误? 我的猜测是,如果 i 将首先装箱为Integer并扩大为Number,结果将始终为false。 在将基元与对象进行比较时,有任何指定 == 运算符的规范吗?将始终尝试执行拆箱,并且必然会扩大?
答案 0 :(得分:9)
Per the JLS,==
盒装和未装箱的值之间的比较会导致取消装箱转换,而不是相反(或者您将使用引用相等) ,而不是价值平等)。编译器无法取消打开普通Number
; Number
本身并非“可转换为a numeric type。”
Java 7编译器似乎很聪明。此版本的程序集输出以及将比较移动到private
方法的版本完全忽略声明的类型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
答案 1 :(得分:3)
您的问题中的代码使用Eclipse编译器为Java 6和7提供了“不兼容的操作数类型Number和int”。使用Oracle Java 7 SDK中的javac
,它可以编译并打印true
。
为什么?
作业Number n = 10
将转换为Number n = Integer.valueOf(10);
稍后,编译器将创建n == Integer.valueOf(10)
(自动装箱int值)。
这是真的,因为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
答案 2 :(得分:1)
Oracle JDK 7中的编译器允许您通过自动装箱原语来编译代码,但这违反了Java语言规范。
根据JLS section 15.21,等式运算符有3种变体:
15.21.1。数值等式算子==和!=
描述等于运算符“如果等于运算符的操作数都是数值类型,或者一个是数字类型而另一个是可转换的(第5.1.8节)到数字类型”。在这种情况下,其中一个操作数具有数字类型,但另一个操作数不能转换为数字类型(拆箱)。
15.21.2。布尔等式运算符==和!=
描述等于运算符“如果等于运算符的操作数都是boolean类型,或者一个操作数是boolean类型而另一个是Boolean类型”。事实并非如此。
15.21.3。参考等式运算符==和!=
描述等于运算符“如果等于运算符的操作数都是引用类型或空类型”。事实并非如此。
由于==运算符的操作数与JLS中定义的三种变体中的任何一种都不匹配,因此代码不应该编译。
Java 7编译器实际上做的是通过自动装箱原始操作数来使用引用相等运算符,然后比较引用。由于此变量没有记录使用自动装箱(根据§5.1.7转换为参考类型),如为数字相等运算符指定了拆箱(第5.1.8节),编译器应用未记录的功能JLS。
在JDK 5和6中有这种行为bug,据称已修复。在Java 7中,一些自动转换和装箱/拆箱案例were added to the JLS我几乎认为有人打算为相等运算符添加自动装箱并将错误留在编译器中而不实际更新JLS。