如果Scala中的equals方法应该实现原始的Java boolean Object.equals(Object x)方法,我认为它应该写成def equals(that: AnyRef): Boolean。
IntelliJ生成的是def equals(that: Any): Boolean。我在网上也遇到过使用Any而不是AnyRef的例子。
我应该定义参数的类型是Any还是AnyRef
我之所以这样问,是因为我想实现编写this eq that的方法,但如果that的类型是Any,它就不起作用,我需要首先在AnyRef或特定类上进行模式匹配。如果我在我的equals定义中使用AnyRef,它显然可以工作,但是我不确定我在Scala中做的事情是正确的。
发布于 2020-08-12 23:39:13
如前所述,AnyRef的默认equals是引用相等(继承自java.lang.Object,equals方法的签名是(Ljava/lang/Object;)Z )(即java.lang.Object => Boolean的Java字节码(从技术上讲,它是(java.lang.Object, java.lang.Object) => Boolean (第一个j.l.O是this))。
Scala编译器在编译方法参数中的Any/AnyVal/AnyRef时做了一些奇怪的事情。考虑一下:
class Foo {
override def equals(that: Any): Boolean =
that match {
case r: AnyRef => this eq r
case _ => false
}
}并在REPL中使用:javap检查字节码:
public boolean equals(java.lang.Object);
descriptor: (Ljava/lang/Object;)Z
flags: ACC_PUBLIC
Code:
stack=2, locals=5, args_size=2
0: aload_1
1: astore_3
2: aload_3
3: instanceof #4 // class java/lang/Object
6: ifeq 27
9: aload_3
10: astore 4
12: aload_0
13: aload 4
15: if_acmpne 22
18: iconst_1
19: goto 23
22: iconst_0
23: istore_2
24: goto 35
27: goto 30
30: iconst_0
31: istore_2
32: goto 35
35: iload_2
36: ireturn它实际上编译成一个只接受Objects的equals方法(然后执行instanceof...)。
这确实带来了一个问题,当我们试图比较AnyRef和AnyVal的等价性时会发生什么:
class Foo {
override def equals(that: Any): Boolean =
that match {
case c: Char => c == 'M'
case r: AnyRef => this eq r
case _ => false
}
}
object Bar {
def cmpFooWithChar(f: Foo, c: Char): Boolean = f == c
}在Foo.equals中,:javap显示:
3: instanceof #18 // class java/lang/Characterjava.lang.Character是scala.Char包装箱的目标。
在Bar$中
public boolean cmpFooWithChar($line15.$read$$iw$$iw$Foo, char);
descriptor: (L$line15/$read$$iw$$iw$Foo;C)Z
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=3
0: aload_1
1: iload_2
2: invokestatic #39 // Method scala/runtime/BoxesRunTime.boxToCharacter:(C)Ljava/lang/Character;
5: astore_3
6: dup
7: ifnonnull 18
10: pop
11: aload_3
12: ifnull 25
15: goto 29
18: aload_3
19: invokevirtual #43 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
22: ifeq 29
25: iconst_1
26: goto 30
29: iconst_0
30: ireturn注意编译器是如何处理==操作的:
如果已装箱的对象或Character
Char均为非,则它将调用equals方法到底发生了什么魔术?井,
def cmp(f: Foo, a: Any): Boolean = f == a编译为(Foo, java.lang.Object) => Boolean签名。所以,有趣的是,是这样的:
def cmp(f: Foo, v: AnyVal): Boolean是的,您没看错:AnyVal、AnyRef和Any作为函数参数类型在编译为字节码时都等同于java.lang.Object,编译器会自动将Int装箱。
有点奇怪的是,Scala编译器在instanceof java.lang.Object指令中留下了一些东西,尽管我很怀疑JIT会优化掉检查。这确实有一个有趣的效果:如果我们在Foo.equals的第二个实现中交换了Char和AnyRef用例,那么Char用例实际上就变成了死代码,因为它位于instanceof java.lang.Object之后。然而,我怀疑编译器的死码检查是在Scala类型上进行的,而不管JVM类型是什么。
对于这些示例,如果能看到ScalaJS发出的JS或ScalaJS发出的LLVM,那将会非常有趣。
https://stackoverflow.com/questions/63378512
复制相似问题