首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >equals的签名应该是equals(x: Any)还是equals(x: AnyRef)

equals的签名应该是equals(x: Any)还是equals(x: AnyRef)
EN

Stack Overflow用户
提问于 2020-08-12 22:19:18
回答 1查看 77关注 0票数 0

如果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中做的事情是正确的。

EN

回答 1

Stack Overflow用户

发布于 2020-08-12 23:39:13

如前所述,AnyRef的默认equals是引用相等(继承自java.lang.Objectequals方法的签名是(Ljava/lang/Object;)Z )(即java.lang.Object => Boolean的Java字节码(从技术上讲,它是(java.lang.Object, java.lang.Object) => Boolean (第一个j.l.Othis))。

Scala编译器在编译方法参数中的Any/AnyVal/AnyRef时做了一些奇怪的事情。考虑一下:

代码语言:javascript
复制
class Foo {
  override def equals(that: Any): Boolean =
    that match {
      case r: AnyRef => this eq r
      case _ => false
    }
}

并在REPL中使用:javap检查字节码:

代码语言:javascript
复制
  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...)。

这确实带来了一个问题,当我们试图比较AnyRefAnyVal的等价性时会发生什么:

代码语言:javascript
复制
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显示:

代码语言:javascript
复制
    3: instanceof    #18                 // class java/lang/Character

java.lang.Characterscala.Char包装箱的目标。

Bar$

代码语言:javascript
复制
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

  • checks的Char均为非,则它将调用equals方法

到底发生了什么魔术?井,

代码语言:javascript
复制
def cmp(f: Foo, a: Any): Boolean = f == a

编译为(Foo, java.lang.Object) => Boolean签名。所以,有趣的是,是这样的:

代码语言:javascript
复制
def cmp(f: Foo, v: AnyVal): Boolean

是的,您没看错:AnyValAnyRefAny作为函数参数类型在编译为字节码时都等同于java.lang.Object,编译器会自动将Int装箱。

有点奇怪的是,Scala编译器在instanceof java.lang.Object指令中留下了一些东西,尽管我很怀疑JIT会优化掉检查。这确实有一个有趣的效果:如果我们在Foo.equals的第二个实现中交换了CharAnyRef用例,那么Char用例实际上就变成了死代码,因为它位于instanceof java.lang.Object之后。然而,我怀疑编译器的死码检查是在Scala类型上进行的,而不管JVM类型是什么。

对于这些示例,如果能看到ScalaJS发出的JS或ScalaJS发出的LLVM,那将会非常有趣。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63378512

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档