Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange value comparison issue in Kotlin, "===" returns true but "==" returns false

I have encountered a very strange value comparison issue in Kotlin that I cannot explain, the following code prints false

data class Foo (
    val a: Byte
)
fun main() {
    val NUM: Byte = 1
    var m: Foo? = Foo(NUM)
    println(m?.a == NUM)
}

But if I change the last line to

println(m?.a === NUM)

or

println(m!!.a == NUM)

it prints true, I'm so confused, anyone could help to explain? Thanks.

like image 844
Xiaoyu Yu Avatar asked Jul 13 '21 03:07

Xiaoyu Yu


People also ask

What is the difference between== and=== Kotlin?

Equality In Kotlin there are two types of equality: Structural equality ( == - a check for equals() ) Referential equality ( === - two references point to the same object)

What == exactly do in Kotlin in comparison to Java?

Structural Equality ('==') == operator in Kotlin only compares the data or variables, whereas in Java or other languages == is generally used to compare the references. The negated counterpart of == in Kotlin is != which is used to compare if both the values are not equal to each other.

How to compare 2 objects in Kotlin?

In Kotlin, == is the default way to compare two objects: it compares their values by calling equals under the hood. Thus, if equals is overridden in your class, you can safely compare its instances using ==. For reference comparison, you can use the === operator, which works exactly the same as == in Java.

What does:: mean in Kotlin?

:: converts a Kotlin function into a lambda. this translates to MyClass(x, y) in Kotlin.

How to compare two strings in Kotlin?

Another method to compare two strings in Kotlin is to use the equals () function. This comparison is case sensitive. For case-insensitive string comparison in Kotlin, pass the second argument as True. 3. Using CompareTo () function Kotlin has another method, CompareTo (), which is used to check the order of two strings.

What is the negated counterpart of == in Kotlin?

The negated counterpart of ==in Kotlin is !=which is used to compare if both the values are not equal to each other. Referential equality (‘===’) ===operator is used to compare the reference of two variable or object. It will only be trueif both the objects or variables pointing to the same object.

How do you check for equality in Kotlin?

In Kotlin, == is used to check the structural equality of two objects. It will return true if both the objects have the same value: Koltin uses the === operator for referential equality. It returns true if the two variables are pointing to the same object and have the same value.

What is the Boolean value of an expression in Kotlin?

The Boolean value of an expression is the basis for all Kotlin comparisons and conditions. You will learn more about conditions in the next chapter.


1 Answers

The issue only appears in version 1.5.20, while 1.5.10 is not affected.

This seems to be an issue in the newer kotlin compiler version.

With some bytecode, we can explain the problem (data class was called Blah, func was called blah).

This is the bytecode, compiled with 1.5.10, that returns True for println(m?.a == NUM) - everything seems to be fine. We're doing a primitive not equals of the two numbers, which returns False (correct since 1 != 1 is 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
}

However in Version 1.5.20, the bytecode instructs for an object comparison using the JVMs Intrinsics.areEqual on a boxed Integer with content 1 and a boxed Byte with content 1, which will return False, since it uses equals on Byte. This is the cause of this issue. The compiler devs surely wanted a True at this point.

But why does this evaluate to false? Here's a snippet of the Byte.equals's description "The result is true if and only if the argument is not null and is a Byte object that contains the same byte value as this object."

...and the bytecode for explaination:

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
}

UPDATE

The guys from jetbrains commented the issued ticket https://youtrack.jetbrains.com/issue/KT-47717 with "That's definitely a bug." and priority major.

like image 185
Dominik Avatar answered Oct 20 '22 16:10

Dominik