Странная проблема сравнения значений в Котлине «===» возвращает значение true, но «==» возвращает значение false

#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