Why == operator and equals() behave differently fo

2020-02-04 02:29发布

问题:

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

The expression x == that is equivalent to if (x eq null) that eq null else x.equals(that) http://www.scala-lang.org/api/current/#scala.Any

For objects of subclasses of AnyRef, I can understand it easily, and I didn't see any strange things.

However, for values of AnyVal, (I mean Int, Double, Long, and so on,) the above definition is somewhat tricky (1 eq null? This does not compile if we do not convert 1 to java.lang.Integer). Also, == and equals() behave differently.

I'll give some examples.

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

So far, nothing is strange. But if we do the same things with equals() methods,

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

So if the types are different, equals() always returns false, whereas == tests if they represent the same value if they are converted to the same type.

In the case of subclass of AnyRef, methods == and equals() return the same.

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) equals 2
res28: Boolean = true

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

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

So, why methods == and equals() are diffrent for AnyVal?

I'm using Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_25).

EDIT 1
I saw that == cannot be overriden directly, as it is defined as a final method in class Any according to Programming in Scala, 2nd Edition.

EDIT 2
Although there is an answer, my question remains. I will leave this question open.

What correspond to scala.Int and scala.Long in Java are Java's primitive types int and long.
In Java, java.lang.Integer and java.lang.Long are classes, so their variables are references, which can have null. That means, they are like AnyRef in Scala. Not AnyVal.
Scala's AnyVal - scala.Int and scala.Long cannot have null values, neither can Java's int and long.
Also, java.lang.Integer's == in Java is for reference equality (same as eq in Scala).
What you get using java.lang.Integer in Scala REPL will be quite different from what you get with it in pure Java Project with .java source file in this respect.

However, what I could get from using classes of primitive types in Java was: (THIS IS JAVA)

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))));
    }
}

output:

true
false
false
true
false
Yes, they behave similar to scala AnyVal's equals(). But, then, why does this happen?

Does Scala's AnyVal's == correspond to == of Java's primitive type
and does Scala's AnyVal's equals() correspond to equals() of Java's class types?
What about equality tests with BigInt? There is no corresponding primitive type in Java.
The question remains...

EDIT 3
I could find some information from scaladoc. (http://www.scala-lang.org/api/current/index.html#scala.Int)
The Implicit information from the item of Shadowed Implicit Value Members,
I could find == was overloaded for Char, Short, Float, and ...,
and == will call implicit conversions int2double, int2float, or int2long.
Whereas equals() is only defined for Any, and it will call implicit conversion int2Integer.
That is, Int.equals() will be the same as java.lang.Integer.equals().

One question remains:
Why == of AnyVal is overloaded, and equals() of AnyVal is not overloaded?

回答1:

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
}


回答2:

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