问题描述
如果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
或特定类。如果我在AnyRef
定义中使用equals
,则显然可以正常工作,但不确定在Scala中是否做对了。
解决方法
如前所述,equals
的默认AnyRef
是引用相等(从java.lang.Object
继承,equals
方法的签名是(Ljava/lang/Object;)Z
(即Java java.lang.Object => Boolean
的字节码(技术上为(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
实际上,它编译为仅占用equals
个(然后执行Object
...)的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/Character
java.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
请注意编译器如何处理==
操作:
- 将
Char
装到Character
- 检查盒装对象还是
Foo
是null
- 如果两者都不为空,则调用
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将优化检查。这确实产生了有趣的效果:如果我们在Char
的第二个实现中交换AnyRef
和Foo.equals
情况,则Char
情况实际上变成了无效代码,因为它在{之后{1}}。我怀疑编译器的无效代码检查是对Scala类型进行的,而与JVM类型无关。
对于这些示例,看到ScalaJS发出的JS或Scala Native发出的LLVM IR真的很有趣。