3
java.lang.StackOverflowError
    at kotlin.jvm.internal.Intrinsics.areEqual(Intrinsics.java:164)
    at plugin.interaction.inter.teleports.Category.equals(Category.kt)
    at kotlin.jvm.internal.Intrinsics.areEqual(Intrinsics.java:164)
    at plugin.interaction.inter.teleports.Destination.equals(Destination.kt)

Happens from a .equals comparison between two non-relationship data classes.

Major bug.

data class Category(val name: String, val destinations: MutableList<Destination>)

data class Destination(val category: Category, val name: String)
Jire
  • 7,747
  • 11
  • 42
  • 75

2 Answers2

4

Data classes in Kotlin are just syntactic sugar for Java POJOs.

The main culprit in your example is this cycle:

val destinations: MutableList<Destination> in Category &
val category: Category in Destination

You must remove this cycle by moving either of the two variables out of the primary data class constructor.

However, there is also a much bigger sideeffect: data class Category(..) is mutable, which will cause for it (and any other data class using categories in it's primary constructor!) to be unsafe to use as keys in any hash-based collection. For more information, see: Are mutable hashmap keys a dangerous practice?

Given that data classes are meant for pure data, I recommend removing val category: Category in data class Destination(..), and change type of val destinations: MutableList<Destination> in data class Category(..) to read-only List<Destination>. In order to break immutable state after said changes, you will have to either perform unsafe casts from Kotlin or create an instance of the class from Java.

If you however absolutely require a backreference to categories in destinations (and aren't using your classes in hashmaps/-sets/etc.), you could either make Destination a regular class and implement equals/hashCode yourself, or move the category out of the primary constructor. This is a bit tricky, but can be done with a secondary constructor:

data class Destination private constructor(val name: String) {
    private lateinit var _category: Category
    val category get() = _category
    constructor(category: Category, name: String) : this(name) {
        _category = category
    }
}
F. George
  • 3,912
  • 3
  • 17
  • 30
4

Well in my case I was overriding equals method like:

override fun equals(other: Any?): Boolean {
        // some code here
        if (other==this)
            return true
       // some code here
    }

equals and == in java

In java when we use equals(for ex: str1.equals(str2)) it checks the content of two object(for custom objects you must have to override equals and check all the values of objects otherwise Object class's equals method just compare reference, which is same as ==), but if we use ==(for ex: str1==str2) operator, it checks the reference of both objects.


== in kotlin

But in case of kotlin when we use == operator, it checks the content(data or variable) of objects only if they are object of data class. And == operator checks reference for normal class.

when we use == it will call the equals method.


So in my overridden equals method when other==this will execute it will call eaquals method again, and that will call eaquals method again and make an infinite loop.

So to make it work we need to change == to ===(this will check the reference of both operator), like:

 if (other===this)
     return true

Note: .equals and == are same until we use them with Float or Double. .equals disagrees with the IEEE 754 Standard for Floating-Point Arithmetic, it returns a false when -0.0 was compared with 0.0 whereas == and === returns true

You can check reference here

Suraj Vaishnav
  • 5,731
  • 3
  • 29
  • 40