为什么在Scala中,对于AnyVal类型的值,==运算符和equals()方法的行为不同?

15
scala.Any的scaladoc中,解释了运算符 == (或方法 == ):

  

表达式x == that 等同于 if(x eq null)that eq null else x.equals(that)    http://www.scala-lang.org/api/current/#scala.Any

对于AnyRef 的子类对象,我可以轻松理解它,没有看到任何奇怪的东西。
但是,对于AnyVal 的值(我的意思是Int Double , Long 等),上述定义有些棘手( 1 eq null ?如果我们不将 1 转换为java.lang.Integer,这不会编译)。另外, == 和 equals()的行为不同。
我将给出一些例子。
scala> 1 == 1
res0:Boolean = true

scala> 1 == 1.0
res1:Boolean = true

scala> 1 == 1.2
res2:Boolean = false

scala> 2 == BigInt(2)
res3:Boolean = true

scala> 2.0 == BigInt(2)
res4:Boolean = true

scala> 2 == BigInt(3)
res5:Boolean = false


到目前为止,没有什么奇怪的地方。但是如果我们使用 equals()方法进行相同的操作,

scala> 1 equals 1
res7:Boolean = true

scala> 1 equals 1.0
res8:Boolean = false

scala> 1 equals 1.2
res9:Boolean = false

scala> 2 equals BigInt(2)
res10:Boolean = false

scala> 2.0 equals BigInt(2)
res11:Boolean = false

scala> 2 equals BigInt(3)
res12:Boolean = false


因此,如果类型不同,则equals()始终返回false,而==测试它们是否表示相同的值(如果将它们转换为相同的类型)。
AnyRef 的子类中,方法 == 和 equals()返回相同的结果。
scala> BigInt(2)== 2
res25:Boolean = true

scala> BigInt(2)== 2.0
res26:Boolean = true

scala> BigInt(3)== 2
res27:Boolean = false

scala> BigInt(2)等于2
res28:Boolean = true

scala> BigInt(2)等于2.0
res29:Boolean = true

scala> BigInt(3)等于2
res30:Boolean = false


那么,为什么对于AnyVal ,方法 == 和 equals()是不同的?
我使用的是Scala版本2.10.2(Java HotSpot(TM)64位服务器VM,Java 1.7.0_25)。 编辑1
我看到==不能直接覆盖,因为根据在Scala中编程,第2版,它被定义为类Any中的final方法。 编辑2
尽管有答案,但我的问题仍然存在。 我会留下这个问题打开。

在Java中,scala.Int 和 scala.Long 对应于Java的原始类型 int 和 long 。 在Java中, java.lang.Integer 和
class Main {
    public static void main(String[] args) {
        System.out.println(String.valueOf(new java.lang.Integer(1).equals(1)));
        System.out.println(String.valueOf(new java.lang.Integer(1).equals(1L)));
        System.out.println(String.valueOf(new java.lang.Integer(1).equals(1.0)));
        System.out.println(String.valueOf(new java.lang.Integer(1).equals(new java.lang.Integer(1))));
        System.out.println(String.valueOf(new java.lang.Integer(1).equals(new java.lang.Long(1))));
    }
}

输出:

true
false
false
true
false

是的,它们的行为类似于Scala的AnyVal的equals()方法。但是,为什么会这样呢?

Scala的AnyVal==方法是否对应于Java的原始类型的==方法?
Scala的AnyValequals()方法是否对应于Java的类类型的equals()方法?
那么使用BigInt进行相等性测试呢?Java中没有对应的原始类型。
问题仍然存在...

编辑3
我从scaladoc中找到了一些信息。(http://www.scala-lang.org/api/current/index.html#scala.Int
Shadowed Implicit Value Members项的Implicit information中,我发现==被重载用于CharShortFloat等等,
并且==将调用隐式转换int2doubleint2floatint2long
equals()仅针对Any定义,它将调用隐式转换int2Integer
也就是说,Int.equals()将与java.lang.Integer.equals()相同。

一个问题仍然存在:
为什么AnyVal==被重载了,而equals()没有被重载?


很抱歉,但是我在整篇文章之后感到困惑。您能否请在最后明确具体问题是什么? - Jatin
@Jatin 在 Scala 中,== 方法和 equals() 方法在 AnyRef 类型的值中是相同的。我认为它们在 AnyVal 类型的值中也应该是相同的。但实际上它们是不同的。然而,在我学习 Scala 的时候,我找不到任何关于这个问题的资料。那么,为什么 ==equals()AnyVal 中不是相同的呢?是否有相关规范说明? - Naetmul
@Naetmul,这个输出怎么样:println(Double.NaN == Double.NaN) println(Double.NaN equals Double.NaN),我期望是 true 和 true,但实际上输出是 false true,不理解它,任何帮助将不胜感激!!! - Aamir
2个回答

11

相关讨论包括描述性的

2010年的规范

和推测性的

2011年重新思考平等

顺便提一下,该规范在12.2节中指出数值类型的相等性质

或者,在HTML中。 (底部引用,如下所示。)

在他2010年的“混合规范”中,Paul Phillips这样表达:

比较两个原始类型(装箱或未装箱)使用==应该总是给出与比较这些值作为未装箱原始类型得到的结果相同。当您直接调用equals时,您跳过了所有这些软化逻辑,而是被视为Java理论中不同类型的两个装箱值始终不相等。规范没有提到装箱原始类型,除了12.5中对Predef提供的转换的简单提及。通常情况下,你不应该意识到一个原始类型存储在其“装箱”形式中,除非你出于性能原因需要这样做。因此,例如,这些值会自动解包并为您推广:
scala> val ds = List(7.0)
ds: List[Double] = List(7.0)

scala> val is = List(7)
is: List[Int] = List(7)

scala> ds(0) == is(0)
res24: Boolean = true

scala> :javap -
  Size 1181 bytes
  MD5 checksum ca732fd4aabb301f3ffe0e466164ed50
  Compiled from "<console>"
[snip]
     9: getstatic     #26                 // Field .MODULE$:L;
    12: invokevirtual #30                 // Method .ds:()Lscala/collection/immutable/List;
    15: iconst_0      
    16: invokevirtual #36                 // Method scala/collection/immutable/List.apply:(I)Ljava/lang/Object;
    19: invokestatic  #42                 // Method scala/runtime/BoxesRunTime.unboxToDouble:(Ljava/lang/Object;)D
    22: getstatic     #47                 // Field .MODULE$:L;
    25: invokevirtual #50                 // Method .is:()Lscala/collection/immutable/List;
    28: iconst_0      
    29: invokevirtual #36                 // Method scala/collection/immutable/List.apply:(I)Ljava/lang/Object;
    32: invokestatic  #54                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
    35: i2d           
    36: dcmpl     

我有点惊讶你会注意到这个

2.0 == BigInt(2)  // So far, nothing is strange.

对我来说,这有点神奇。正如Paul Phillips所描述的那样,它调用了BoxesRunTime.equals
     9: ldc2_w        #22                 // double 2.0d
    12: invokestatic  #29                 // Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
    15: getstatic     #34                 // Field scala/package$.MODULE$:Lscala/package$;
    18: invokevirtual #38                 // Method scala/package$.BigInt:()Lscala/math/BigInt$;
    21: iconst_2      
    22: invokevirtual #44                 // Method scala/math/BigInt$.apply:(I)Lscala/math/BigInt;
    25: invokestatic  #48                 // Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z

这是规范,供参考,以此形式基本上承诺做正确的事情:
equals 方法测试参数是否为数字值类型。如果是,则将执行适用于该类型的 == 操作。也就是说,可以将数字值类型的 equals 方法视为以下定义:
def equals(other: Any): Boolean = other match {
  case that: Byte   => this == that
  case that: Short  => this == that
  case that: Char   => this == that
  case that: Int    => this == that
  case that: Long   => this == that
  case that: Float  => this == that
  case that: Double => this == that
  case _ => false
}

2

我认为这是由于自动装箱和保持与Java及数学等领域的期望一致性所导致的,例如1 = 1.0 = 1(表示为long类型)等。例如,在Scala数值类型和Java数值类型之间进行比较:

scala> val foo: Long = 3L
foo: Long = 3

scala> val bar: Int = 3
bar: Int = 3

scala> foo == bar
res0: Boolean = true

scala> foo.equals(bar)
res1: Boolean = false

相比之下:

scala> val jfoo = new java.lang.Long(3L)
jfoo: Long = 3

scala> val jbar = new java.lang.Integer(3)
jbar: Integer = 3

scala> jfoo == jbar
res2: Boolean = true

scala> jfoo.equals(jbar)
res3: Boolean = false

在真正的Java中,new java.lang.Long(3L)new java.lang.Integer(3)都是引用。此外,java.lang.Long不能被强制转换为java.lang.Integer,否则会抛出java.lang.ClassCastException异常。如果在将这些引用转换为同一类型之前进行比较,甚至不会编译:它会生成编译错误:“incomparable types: java.lang.Integer and java.lang.Long”。我认为java.lang.Long将自动转换为scala.Long,而java.lang.Integer将自动转换为scala.Int。上述代码在纯Java中无法运行。 - Naetmul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接