Why == operator and equals() behave differently for values of AnyVal in Scala

后端 未结 2 405
野趣味
野趣味 2020-12-09 04:37

In the scaladoc of scala.Any, the operator == (or, method ==) is explained:

The expression x == that

相关标签:
2条回答
  • 2020-12-09 05:23

    I expect this was done because of auto-boxing and a desire to stay consistent with expectations held over from Java, and maths in generel (1 = 1.0 = 1 (represented as a long) etc.). For example, running comparisons between Scala numeric types and Java numeric types:

    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
    

    Compared with:

    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
    
    0 讨论(0)
  • 2020-12-09 05:24

    The relevant discussions are the descriptive

    spec for == from 2010

    and the speculative

    Rethinking equality from 2011

    FWIW, the spec calls out equality for numeric value types in 12.2.

    Or, in HTML. (Quote at bottom, below.)

    In his "pidgin spec-ese" of 2010, Paul Phillips puts it this way:

    comparing two primitives (boxed or unboxed) with == should always give the result you would have gotten by comparing those values as unboxed primitives. When you call equals directly, you are skipping all that softening logic and instead treated to java's theory that two boxed values of different types are always unequal.

    The spec doesn't speak of boxed primitives, aside from a passing reference in 12.5 to the conversions provided by Predef. You're not generally meant to be aware of when a primitive is stored in its "boxed" form, unless of course you need to for performance reasons.

    So, for example, these values are silently unboxed and promoted for you:

    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     
    

    I'm kind of surprised that you note

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

    To me, that's slightly magical. It calls into BoxesRunTime.equals as described by Paul Phillips.

         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
    

    Here is the spec, for reference, which in this form basically just promises to do the right thing:

    The equals method tests whether the argument is a numeric value type. If this is true, it will perform the == operation which is appropriate for that type. That is, the equals method of a numeric value type can be thought of being defined as follows:

    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
    }
    
    0 讨论(0)
提交回复
热议问题