好吧,我对 identical?
的思维模型不准确。它调用了 clojure.lang.Util/identical
,其实现如下
static public boolean identical(Object k1, Object k2){
return k1 == k2;
}
当 Clojure 以此签名调用函数时,它必须在参数中对原始值进行包装。它们通过调用 Double.valueOf(double)
对 Double 进行包装。NaN 的非寻常的等价语义使得示例变得复杂,但在所有情况下,identical?
比较的是已包装值的引用等价性,而不是数值等价性。
通过查看反汇编的字节码(见下文),我们可以看到发生了什么。以下为摘要。
- 在示例 1、2 和 4 中,每个传递给
identical?
的参数都是单独进行包装的。
- 在示例 3 中,
##NaN
仅被包装一次,并且相同的已包装值被用作 identical?
的两个参数。
这里有一些更多示例。请注意,##Inf
的行为与##NaN
的行为相匹配。
(identical? ##Inf ##Inf) ; true
(let [x ##Inf] (identical? x x)) ; false
(identical? 1.0 1.0) ; false
(let [x 1.0] (identical? x x)) ; false
(def my-one 1.0)
(identical? my-one my-one) ; true
结论
一切如预期所示。我对identical?
的心理模型是错误的。它更像是(Object)... == (Object) ...
而不是普通的==
。
经验教训:仅使用identical?
进行引用相等性比较。
拆解示例
代码已使用clj-java-decompiler拆解。
示例 1 (identical? Double/NaN Double/NaN)
class user$fn_line_1__254
Minor version: 0
Major version: 52
Flags: PUBLIC, FINAL, SUPER
public void <init>();
Flags: PUBLIC
Code:
linenumber 1
0: aload_0
1: invokespecial clojure/lang/AFunction.<init>:()V
4: return
public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: getstatic java/lang/Double.NaN:D
3: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
linenumber 1
6: getstatic java/lang/Double.NaN:D
9: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
linenumber 1
12: invokestatic clojure/lang/Util.identical:(Ljava/lang/Object;Ljava/lang/Object;)Z
15: ifeq 24
18: getstatic java/lang/Boolean.TRUE:Ljava/lang/Boolean;
21: goto 27
24: getstatic java/lang/Boolean.FALSE:Ljava/lang/Boolean;
27: areturn
StackMapTable: 00 02 18 42 07 00 1D
public java.lang.Object invoke();
Flags: PUBLIC
Code:
linenumber 1
0: invokestatic user$fn_line_1__254.invokeStatic:()Ljava/lang/Object;
3: areturn
static {};
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: return
示例 2 (let [x Double/NaN] (identical? x x))
跳过;与示例 4 类似。
示例 3 (identical? ##NaN ##NaN)
请注意,装箱是在静态块中发生的。
class user$fn_line_1__246
Minor version: 0
Major version: 52
Flags: PUBLIC, FINAL, SUPER
public static final java.lang.Object const__1;
Flags: PUBLIC, STATIC, FINAL
public void <init>();
Flags: PUBLIC
Code:
linenumber 1
0: aload_0
1: invokespecial clojure/lang/AFunction.<init>:()V
4: return
public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: getstatic user$fn_line_1__246.const__1:Ljava/lang/Object;
3: getstatic user$fn_line_1__246.const__1:Ljava/lang/Object;
linenumber 1
6: invokestatic clojure/lang/Util.identical:(Ljava/lang/Object;Ljava/lang/Object;)Z
9: ifeq 18
12: getstatic java/lang/Boolean.TRUE:Ljava/lang/Boolean;
15: goto 21
18: getstatic java/lang/Boolean.FALSE:Ljava/lang/Boolean;
21: areturn
StackMapTable: 00 02 12 42 07 00 17
public java.lang.Object invoke();
Flags: PUBLIC
Code:
linenumber 1
0: invokestatic user$fn_line_1__246.invokeStatic:()Ljava/lang/Object;
3: areturn
static {};
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: ldc2_w NaN
3: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
6: putstatic user$fn_line_1__246.const__1:Ljava/lang/Object;
9: return
示例 4 (let [x ##NaN] (identical? x x))
注意invokeStatic方法中的两个Double.valueOf
调用。
class user$fn_line_1__250
Minor version: 0
Major version: 52
Flags: PUBLIC, FINAL, SUPER
public void <init>();
Flags: PUBLIC
Code:
linenumber 1
0: aload_0
1: invokespecial clojure/lang/AFunction.<init>:()V
4: return
public static java.lang.Object invokeStatic();
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: ldc2_w NaN
3: dstore_0 /* x */
4: dload_0 /* x */
5: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
8: dload_0 /* x */
9: invokestatic java/lang/Double.valueOf:(D)Ljava/lang/Double;
linenumber 1
12: invokestatic clojure/lang/Util.identical:(Ljava/lang/Object;Ljava/lang/Object;)Z
15: ifeq 24
18: getstatic java/lang/Boolean.TRUE:Ljava/lang/Boolean;
21: goto 27
24: getstatic java/lang/Boolean.FALSE:Ljava/lang/Boolean;
27: areturn
StackMapTable: 00 02 FC 00 18 03 42 07 00 1B
public java.lang.Object invoke();
Flags: PUBLIC
Code:
linenumber 1
0: invokestatic user$fn_line_1__250.invokeStatic:()Ljava/lang/Object;
3: areturn
static {};
Flags: PUBLIC, STATIC
Code:
linenumber 1
0: return