Travis Griggs
Travis Griggs

Reputation: 22252

How to implement equals() for related subtypes in Kotlin

I'm trying to implement a class tree of related types (for example, e.g AST nodes) with a common abstract superclass. I'm trying to implement equals() on the subnodes so that different sub types are indeed different, but two like types can do a more introspective computation of equality. I've tried this:

abstract class Something {
    abstract fun equals(other:Something) : Boolean 
}

class Foo(val value:Int):Something() {
    override fun equals(other:Something) : Boolean {
        return (other as Foo).value == value
    }
}

class Bar(val value:Int):Something() {
    override fun equals(other:Something) : Boolean {
        return (other as Bar).value == value
    }
}

fun main(args: Array<String>) {
    val foo1:Something = Foo(1)  // if i don't type these as the abstract type
    val foo2:Something = Foo(1)  // then I it won't even let me check
    val bar1:Something = Bar(42) // if bar1 == foo1, because they're inferred
    val bar2:Something = Bar(42) // as different types
    println("foo1 == foo2 ${foo1 == foo2}") // should print true
    println("bar1 == bar2 ${bar1 == bar2}") // should print true
    println("foo1 == bar1 ${foo1 == bar2}") // should print false
}

Unfortunately, all of the println's just show false though. What am I doing wrong?wrong?

Upvotes: 0

Views: 295

Answers (3)

Konrad Botor
Konrad Botor

Reputation: 5033

When you use syntax foo1 == foo2 Kotlin calls function:

open operator fun equals(other: Any?): Boolean 

of class Any and not your custom equals implementation.

You need to use syntax foo1.equals(foo2) to do what you want. Also, like Alexey Romanov pointed in his answer, you cannot cast Foo to Bar and vice versa, you need to do:

override fun equals(other:Something) : Boolean {
        return when(other) {
         is Foo -> other.value == value
         is Bar -> other.value == value
         else false
        }
    }

in both Foo and Bar classes.

Upvotes: 1

Pawel
Pawel

Reputation: 17248

If you want == to work properly, You have to override operator fun equals(other: Any?) : Boolean.

If you want to explicitly require implementation in subclasses so you don't forget about it, you can mark it as abstract in the superclass:

abstract class Something {
    abstract override operator fun equals(other: Any?) : Boolean
}

class Foo(val value:Int):Something() {
    override fun equals(other: Any?): Boolean {
        return (other as Foo).value == value
    }
}

class Bar(val value:Int):Something() {
    override fun equals(other: Any?): Boolean {
        return (other as Bar).value == value
    }
}

Upvotes: 2

Alexey Romanov
Alexey Romanov

Reputation: 170723

  1. == calls the Object's equals(Any?) method, not your overload. So you need to have override operator fun equals(other: Any?) everywhere.

  2. Your implementations will throw exception instead of returning false for wrong types, that's bad. Instead you need

    // in Foo
    override fun equals(other: Any?) : Boolean = when (other) {
        is Foo -> other.value == value
        else -> false
    }
    

Upvotes: 2

Related Questions