#android #kotlin #equals #equality
Вопрос:
Я столкнулся с очень странной проблемой сравнения значений в Котлине, которую я не могу объяснить, следующий код выводит ложь
data class Foo (
val a: Byte
)
fun main() {
val NUM: Byte = 1
var m: Foo? = Foo(NUM)
println(m?.a == NUM)
}
Но если я изменю последнюю строку на
println(m?.a === NUM)
или
println(m!!.a == NUM)
это правда, я так запутался, кто-нибудь может помочь объяснить? Спасибо.
Комментарии:
1. Я не могу воспроизвести это в скретч-файле Android Studio kotlin. Все три из этих утверждений печати верны для меня.
2. Я думаю, ты ошибаешься. Ваш исходный код также должен печатать true. Примечание сбоку… оператор
===
равенства идентификаторов обычно не полезен для чисел, поскольку классы чисел не имеют стабильных идентификаторов.3. Интересно, что для меня первый случай печатает ложь в Android Studio, также на игровой площадке kotlin: play.kotlinlang.org/… , не могли бы вы попробовать @Matt, @Tenfour04?
4. О, это интересно. Так что это похоже на ошибку, введенную в 1.5.20 (?). Причина, по которой люди не смогли воспроизвести его локально, вероятно, в том, что они не использовали последнюю версию Kotlin. Я могу воспроизвести его с 1.5.20.
5. Я согласен с вами — похоже, это ошибка в 1.5.20. Я открыл выпуск в jetbrains: youtrack.jetbrains.com/issue/KT-47717 . Давайте посмотрим, что произойдет 🙂
Ответ №1:
Проблема появляется только в версии 1.5.20, в то время как 1.5.10 не затронута.
Похоже, это проблема в более новой версии компилятора kotlin.
С помощью некоторого байт-кода мы можем объяснить проблему ( data class
был вызван Blah
, func
был вызван blah
).
Это байт — код, скомпилированный с 1.5.10, который возвращает True
значение println(m?.a == NUM)
-кажется, все в порядке. Мы делаем примитив, не равный двум числам, который возвращает False
(правильно, так 1 != 1
как есть False
).
Compiled from "WtfTest.kt"
public final class de.sfxr.WtfTest {
public de.sfxr.WtfTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public final void blah();
Code:
0: iconst_1
1: istore_1
2: new #14 // class de/sfxr/Blah
5: dup
6: iload_1
7: invokespecial #17 // Method de/sfxr/Blah."<init>":(B)V
10: astore_2
11: aload_2
12: astore_3
13: aload_3
14: invokevirtual #21 // Method de/sfxr/Blah.getA:()B
17: iload_1
18: istore_3
19: iload_3
// PRIMITIVE NOT EQUALS => False
20: if_icmpne 27
23: iconst_1
24: goto 28
27: iconst_0
28: istore_3
29: iconst_0
30: istore 4
32: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream;
35: iload_3
36: invokevirtual #33 // Method java/io/PrintStream.println:(Z)V
39: return
}
Однако в версии 1.5.20 байт-код инструктирует для сравнения объектов с использованием JVM Intrinsics.areEqual
на упакованном Integer
с содержимым 1
и упакованном Byte
с содержимым 1
, которое вернется False
, поскольку оно использует equals
on Byte
. Это основная причина этой проблемы. Разработчики компилятора, безусловно, хотели True
в этот момент.
Но почему это оценивается как ложное? Вот фрагмент описания Byte.equals
«Результат является истинным тогда и только тогда, когда аргумент не равен нулю и является байтовым объектом, содержащим то же значение байта, что и этот объект».
…и байт-код для объяснения:
Compiled from "WtfTest.kt"
public final class de.sfxr.WtfTest {
public de.sfxr.WtfTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public final void blah();
Code:
0: iconst_1
1: istore_1
2: new #14 // class de/sfxr/Blah
5: dup
6: iload_1
7: invokespecial #17 // Method de/sfxr/Blah."<init>":(B)V
10: astore_2
11: aload_2
12: astore_3
13: aload_3
14: invokevirtual #21 // Method de/sfxr/Blah.getA:()B
17: invokestatic #27 // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
20: iload_1
21: invokestatic #32 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
// OBJECT COMPARISON VIA JVM ON BOXED BYTE(1) AND BOXED INT(1) => False
24: invokestatic #38 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
27: istore_3
28: iconst_0
29: istore 4
31: getstatic #44 // Field java/lang/System.out:Ljava/io/PrintStream;
34: iload_3
35: invokevirtual #50 // Method java/io/PrintStream.println:(Z)V
38: return
}
Обновить
Ребята из jetbrains прокомментировали выданный билет https://youtrack.jetbrains.com/issue/KT-47717 с «Это определенно ошибка». и приоритетным направлением.
Так что поздравляю, вы кое-что нашли: -)
Комментарии:
1. стоит ли подавать на это заявление об ошибке? youtrack.jetbrains.com/issues/KT
2. @Simulant уже сделано youtrack.jetbrains.com/issue/KT-47717