Почему == operator и equals() ведут себя по-разному для значений AnyVal в Scala

В scaladoc scala.Any объясняется оператор == (или метод ==):

Выражение 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) equals 2
res28: Boolean = true

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

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

Итак, почему методы == и equals() различаются для AnyVal?

Я использую Scala версию 2.10.2 (Java-сервер HotSpot (TM) 64-Bit Server, Java 1.7.0_25).

РЕДАКТИРОВАТЬ 1
Я видел, что == не может быть переопределен напрямую, поскольку он определен как окончательный метод в классе Any в соответствии с Программирование в Scala, 2nd Edition.

РЕДАКТИРОВАТЬ 2
Хотя есть ответ, мой вопрос остается. Я оставлю этот вопрос открытым.

Что соответствует scala.Int и scala.Long в Java, являются примитивными типами Java Int и Long.
В Java java.lang.Integer и java.lang.Long являются классами, поэтому их переменными являются ссылки, которые могут иметь null. Это означает, что они как AnyRef в Scala. Не AnyVal.
Scala AnyVal - scala.Int и scala.Long не могут иметь значения null, ни Java Int, ни Long.
Кроме того, java.lang.Integer == в Java для ссылочного равенства (то же, что и eq в Scala).
То, что вы используете с помощью java.lang.Integer в Scala REPL, будет сильно отличаться от того, что вы получите с ним в чистом Java-проекте с исходным файлом .java в этом отношении.

Однако то, что я мог получить от использования классов примитивных типов в 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))));
    }
}

Выход:

true
false
false
true
false
Да, они ведут себя подобно Scala AnyVal equals(). Но почему тогда это происходит?

Соответствует ли Scala AnyVal == == примитивного типа Java
и Scala AnyVal equals() соответствует equals() типов классов Java?
Что относительно тестов на равенство с BigInt? В Java нет соответствующего примитивного типа.
Вопрос остается...

РЕДАКТИРОВАТЬ 3
Я мог бы найти некоторую информацию от scaladoc. (http://www.scala-lang.org/api/current/index.html#scala.Int)
Неявная информация из элемента теневых неявных членов,
Я мог найти == был перегружен для Char, Short, Float и...,
и == вызовет неявные преобразования int2double, int2float или int2long.
В то время как equals() определяется только для Any, и он будет вызывать неявное преобразование int2Integer.
То есть Int.equals() будет таким же, как java.lang.Integer.equals().

Остается один вопрос:
Почему == of AnyVal перегружен, а equals() of AnyVal не перегружен?

Ответ 1

Соответствующими обсуждениями являются описательные

spec для == из 2010

и спекулятивный

Пересмотр равенства с 2011 года

FWIW, спецификация вызывает равенство для числовых значений в 12.2.

Или в HTML. (Цитата внизу, внизу.)

В своем "pidgin spec-ese" 2010 года Пол Филлипс говорит так:

сравнение двух примитивов (в коробке или unboxed) с == должно всегда давать результат, который вы получили бы, сравнив эти значения как unboxed примитивы. Когда вы вызываете 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.

Для меня это немного волшебное. Он называет 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

Вот спецификация для ссылки, которая в этой форме в основном просто promises делает правильную вещь:

Метод 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, и математикой в ​​generel (1 = 1.0 = 1 (представлен как длинный) и т.д.). Например, выполняется сравнение между 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