40

生成された equals および hashCode の実装に 1 つまたは 2 つのフィールドのみを含める (または 1 つまたは複数のフィールドを除外する) とします。たとえば、単純なクラスの場合:

data class Person(val id: String, val name: String)

Groovy には次のようなものがあります。

@EqualsAndHashCode(includes = 'id')

ロンボクにはこれがあります:

@EqualsAndHashCode(of = "id")

Kotlinでこれを行う慣用的な方法は何ですか?

これまでの私のアプローチ

data class Person(val id: String) {
   // at least we can guarantee it is present at access time
   var name: String by Delegates.notNull()

   constructor(id: String, name: String): this(id) {
      this.name = name
   }
}

気分が悪いだけですが...私は本当に変更可能にしたくありませんname。また、追加のコンストラクターの定義は醜いです。

4

10 に答える 10

20

私はこのアプローチを使用しました。

data class Person(val id: String, val name: String) {
   override fun equals(other: Person) = EssentialData(this) == EssentialData(other)
   override fun hashCode() = EssentialData(this).hashCode()
   override fun toString() = EssentialData(this).toString().replaceFirst("EssentialData", "Person")
}

private data class EssentialData(val id: String) {
   constructor(person: Person) : this(id = person.id) 
}
于 2017-09-15T20:45:04.590 に答える
0

equals/hashcode の実装については、次の一般的なアプローチを検討してください。以下のコードは、インライン化と kotlin 値クラスを使用しているため、パフォーマンスに影響を与えることはありません。

@file:Suppress("EXPERIMENTAL_FEATURE_WARNING")

package org.beatkit.common

import kotlin.jvm.JvmInline

@Suppress("NOTHING_TO_INLINE")
@JvmInline
value class HashCode(val value: Int = 0) {
    inline fun combineHash(hash: Int): HashCode = HashCode(31 * value + hash)
    inline fun combine(obj: Any?): HashCode = combineHash(obj.hashCode())
}

@Suppress("NOTHING_TO_INLINE")
@JvmInline
value class Equals(val value: Boolean = true) {
    inline fun combineEquals(equalsImpl: () -> Boolean): Equals = if (!value) this else Equals(equalsImpl())
    inline fun <A : Any> combine(lhs: A?, rhs: A?): Equals = combineEquals { lhs == rhs }
}

@Suppress("NOTHING_TO_INLINE")
object Objects {
    inline fun hashCode(builder: HashCode.() -> HashCode): Int = builder(HashCode()).value

    inline fun hashCode(vararg objects: Any?): Int = hashCode {
        var hash = this
        objects.forEach {
            hash = hash.combine(it)
        }
        hash
    }

    inline fun hashCode(vararg hashes: Int): Int = hashCode {
        var hash = this
        hashes.forEach {
            hash = hash.combineHash(it)
        }
        hash
    }

    inline fun <T : Any> equals(
        lhs: T,
        rhs: Any?,
        allowSubclasses: Boolean = false,
        builder: Equals.(T, T) -> Equals
    ): Boolean {
        if (rhs == null) return false
        if (lhs === rhs) return true
        if (allowSubclasses) {
            if (!lhs::class.isInstance(rhs)) return false
        } else {
            if (lhs::class != rhs::class) return false
        }
        @Suppress("unchecked_cast")
        return builder(Equals(), lhs, rhs as T).value
    }
}

これにより、equals/hashcode の実装を統一された方法で簡単に実装/オーバーライドできます。

data class Foo(val title: String, val bytes: ByteArray, val ignore: Long) {
    override fun equals(other: Any?): Boolean {
        return Objects.equals(this, other) { lhs, rhs ->
            this.combine(lhs.title, rhs.title)
                .combineEquals { lhs.bytes contentEquals rhs.bytes }
            // ignore the third field for equals
        }
    }

    override fun hashCode(): Int {
        return Objects.hashCode(title, bytes) // ignore the third field for hashcode
    } 
}
于 2021-10-11T15:39:29.323 に答える