Number n = 10;
int i = 10;
System.out.println(n == i);
基于“你可以框然后加宽”。为什么上面的代码会出现编译时错误?我的猜测是,如果我首先被装箱为 Integer 并扩大到 Number 结果将永远是错误的。在将基元与对象进行比较时,是否有任何规范提到==运算符?将始终尝试执行拆箱并且如果必然会扩大?
Number n = 10;
int i = 10;
System.out.println(n == i);
基于“你可以框然后加宽”。为什么上面的代码会出现编译时错误?我的猜测是,如果我首先被装箱为 Integer 并扩大到 Number 结果将永远是错误的。在将基元与对象进行比较时,是否有任何规范提到==运算符?将始终尝试执行拆箱并且如果必然会扩大?
根据 JLS,==
装箱值和未装箱值之间的比较会导致拆箱转换,而不是相反(否则您将使用引用相等,而不是值相等)。编译器无法拆箱 plain Number
; Number
本身不是“可转换为数字类型”。
Java 7 编译器似乎很聪明。此版本的程序集输出以及将比较移到private
方法中的版本完全忽略声明的 type Number
,并且一切正常。Make that method public
,行为是指定的:转换不是将发生拆箱的列出的类型之一,并且编译器框10
到 anInteger
并通过引用进行比较,这意味着如果您尝试 using Integer.valueOf(10)
,您将true
获得范围 -128..127,如果你使用其他任何东西(另一个宽度,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
您问题中的代码使用 Eclipse 编译器为我提供了 Java 6 和 7 的“不兼容的操作数类型 Number 和 int”。使用javac
Oracle 的 Java 7 SDK,它可以编译和打印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
Oracle JDK 7 中的编译器允许您通过自动装箱原语来编译代码,但这违反了 Java 语言规范。
根据JLS 第 15.21 节,等式运算符有 3 种变体:
15.21.1。数值等式运算符 == 和 !=
描述相等运算符“如果相等运算符的操作数都是数字类型,或者一个是数字类型而另一个是可转换(第 5.1.8 节)为数字类型”。在这种情况下,其中一个操作数具有数字类型,但另一个操作数不能转换为数字类型(拆箱)。
15.21.2。布尔等式运算符 == 和 !=
描述相等运算符“如果相等运算符的操作数都是布尔类型,或者如果一个操作数是布尔类型而另一个是布尔类型”。情况也并非如此。
15.21.3。引用相等运算符 == 和 !=
描述相等运算符“如果相等运算符的操作数既是引用类型又是空类型”。情况也并非如此。
由于 == 运算符的操作数与 JLS 中定义的三个变体中的任何一个都不匹配,因此代码不应编译。
Java 7 编译器实际上所做的是通过自动装箱原始操作数来使用引用相等运算符,然后比较引用。由于此变体未记录使用自动装箱(根据 §5.1.7 转换为引用类型),例如为数值相等运算符指定拆箱(§5.1.8),编译器应用未记录的功能JLS。
在 JDK 5 和 6 中存在这种行为的错误,据称已修复。在 Java 7 中,一些自动转换和装箱/拆箱案例被添加到 JLS 中,我几乎假设有人打算为相等运算符添加自动装箱并将错误留在编译器中而没有实际更新 JLS。