Considering this class: ```open class KotlinPracti...
# getting-started
k
Considering this class:
Copy code
open class KotlinPractice(val id: Int) {
    override fun equals(other: Any?) =
        this === other ||
                other != null &&
                other::class == KotlinPractice::class &&
                (other as KotlinPractice).id == id

    override fun hashCode() = id
}
Is there a reason
other
is not smart-cast and instead I have to cast it to KotlinPractice manually?
l
I can't answer why there is no smartcast, but you can do
this === other || (other as? KotlinPractice)?.id == id
👍 1
e
It's because the compiler doesn't perform smart casts when type checks are based on
::class
. You should use
is
instead:
Copy code
open class KotlinPractice(val id: Int) {
    override fun equals(other: Any?) =
        this === other ||
                other != null &&
                other is KotlinPractice &&
                other.id == id

    override fun hashCode() = id
}
You could then also eliminate the
other != null
check as
is KotlinPractice
is checking that it's not a nullable type either:
Copy code
open class KotlinPractice(val id: Int) {
    override fun equals(other: Any?) =
        this === other ||
                other is KotlinPractice &&
                other.id == id

    override fun hashCode() = id
}
k
Thanks, Eduardo, but I deliberately compared classes to comply with the requirement for
equals
to be symmetric. Unfortunately, when using
is
,
baseObj == derivedObj
does not imply
derivedObj == baseObj
.
👍 1
e
I think only if you override
equals
in
derivedObj
, otherwise it's symmetric:
Copy code
open class KotlinPractice(val id: Int) {
    override fun equals(other: Any?) =
        this === other ||
                other is KotlinPractice &&
                other.id == id

    override fun hashCode() = id
}

class SubKotlinPractice(id: Int) : KotlinPractice(id)

fun main() {
    val kotlinPractice = KotlinPractice(1)
    val subKotlinPractice = SubKotlinPractice(1)
    
    println(kotlinPractice == subKotlinPractice) // true
    println(subKotlinPractice == kotlinPractice) // true
}
x is Y
means "x is of type Y or inherits Y"
v
Klitos is right with his concern. As long as it is not a sealed class, anyone could subclass
KotlinPractice
and overwrite
equals
, thereby most probably violating symmetry
Lukes approach is probably best as long as
id
stays non-nullable
k
Thanks, the fact anyone could override equals and break symmetry was exactly my concern. I think Luke's approach (elegant as it looks) is unfortunately still prone to symmetry violations.
v
Ah, yeah, you are right of course. Did get too less sleep it seems
Maybe this one then:
Copy code
open class KotlinPractice(val id: Int) {
    override fun equals(other: Any?) =
        this === other ||
                other is KotlinPractice &&
                other::class == KotlinPractice::class &&
                other.id == id

    override fun hashCode() = id
}
The
is
also does the null-check, while then provides smart-cast for later. This is especially useful if you have more properties to check
k
Thanks, that's probably the best we can do for now, although this code checks the other class twice. I was wondering if there was any technical reason that the compiler couldn't do a smart cast after comparison of an instance's class.
e
IDK either, but out of curiosity, you could use contracts to achieve smart casting for this use-case:
Copy code
@OptIn(ExperimentalContracts::class)
inline fun <reified T> Any?.isOfType(): Boolean {
  contract {
    returns(true) implies(this@isOfType is T)
  }
  return this != null && this::class == T::class
}

open class KotlinPractice(val id: Int) {
  override fun equals(other: Any?) =
    this === other ||
      other.isOfType<KotlinPractice>() &&
      other.id == id

  override fun hashCode() = id
}
❤️ 5