Почему == 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