简体   繁体   English

Kotlin 等于 hash 代码生成器

[英]Kotlin equals and hash code generator

I am aware that in Kotlin classes will have an equals and hashcode created automatically as follows:我知道在 Kotlin 类中将自动创建一个 equals 和 hashcode,如下所示:

data class CSVColumn(private val index: Int, val value: String) {
}

My question is, is there a way to have the implementation just use one of these properties (such as index ) without writing the code yourself.我的问题是,有没有一种方法可以让实现只使用其中一个属性(例如index )而无需自己编写代码。 What was otherwise a very succinct class now looks like this:原本非常简洁的 class 现在看起来像这样:

data class CSVColumn(private val index: Int, val value: String) {

    override fun equals(other: Any?): Boolean {
        if (this === other) {
            return true
        }
        if (javaClass != other?.javaClass) {
            return false
        }
        other as CSVColumn
        if (index != other.index) {
            return false
        }
        return true
    }

    override fun hashCode(): Int {
        return index
    }

}

In Java with Lombok, I can do something like:在 Java 与龙目岛,我可以做这样的事情:

@Value
@EqualsAndHasCode(of="index")
public class CsvColumn {
    private final int index;
    private final String value;
}

Would be cool if there were a way to tell Kotlin something similar.如果有办法告诉 Kotlin 类似的东西,那就太好了。

From the Data Classes documentation you get: Data Classes文档中,您将获得:

Note that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. 请注意,编译器仅使用主构造函数内定义的属性来生成自动生成的函数。 To exclude a property from the generated implementations, declare it inside the class body 要从生成的实现中排除属性,请在类体内声明它

So you have to implement equals() and hashCode() manually or with the help of a Kotlin Compiler Plugin . 所以你必须手动或在Kotlin编译器插件的帮助下实现equals()hashCode()

You can't do something like this for data classes, they always generate equals and hashCode the same way, there's no way to provide them such hints or options. 你不能为数据类做这样的事情,它们总是以相同的方式生成equalshashCode ,没有办法为它们提供这样的提示或选项。

However, they only include properties that are in the primary constructor, so you could do this for them to only include index : 但是,它们只包含主构造函数中的属性,因此您可以为它们执行此操作以仅包含index

data class CSVColumn(private val index: Int, value: String) {
    val value: String = value
}

... except you can't have parameters in the primary constructor that aren't properties when you're using data classes. ...除了在使用数据类时,不能在主构造函数中使用非属性的参数。

So you'd have to somehow introduce a secondary constructor that takes two parameters, like this: 所以你必须以某种方式介绍一个带有两个参数的辅助构造函数,如下所示:

class CSVColumn private constructor(private val index: Int) {

    var value: String = ""

    constructor(index: Int, value: String) : this(index) {
        this.value = value
    }

}

... but now your value property has to be a var for the secondary constructor to be able to set its value. ...但是现在您的value属性必须是辅助构造函数的var才能设置其值。

All this to say that it's probably not worth trying to work around it. 所有这一切都说可能不值得尝试解决它。 If you need an non-default implementation for equals and hashCode , data classes can't help you, and you'll need to implement and maintain them manually. 如果您需要equalshashCode的非默认实现,则数据类无法帮助您,您需要手动实现和维护它们。


Edit: as @tynn pointed out, a private setter could be a solution so that your value isn't mutable from outside the class: 编辑:正如@tynn所指出的那样,私有的setter可能是一个解决方案,因此你的value在类外是不可变的:

class CSVColumn private constructor(private val index: Int) {

    var value: String = ""
        private set

    constructor(index: Int, value: String) : this(index) {
        this.value = value
    }

}

I wrote a little utility called "stem", which allows you to select which properties to consider for equality and hashing. 我写了一个名为“stem”的小实用程序,它允许您选择要考虑哪些属性进行相等和散列。 The resulting code is as small as it can get with manual equals()/hashCode() implementation: 生成的代码与手动equals()/hashCode()实现一样小:

class CSVColumn(private val index: Int, val value: String)  {
    private val stem = Stem(this, { index })

    override fun equals(other: Any?) = stem.eq(other)
    override fun hashCode() = stem.hc()
}

You can see its implementation here . 你可以在这里看到它的实现。

I guess that we have to write equals()/hashCode() manually for now. 我想我们现在必须手动编写equals()/hashCode() https://discuss.kotlinlang.org/t/automatically-generate-equals-hashcode-methods/3779 https://discuss.kotlinlang.org/t/automatically-generate-equals-hashcode-methods/3779

It is not supported and is planning to be, IMHO. 它不受支持,并且计划成为,恕我直言。

See the following performance optimized way (with the use of value classes and inlining) of implementing a generic equals/hashcode for any Kotlin class:请参阅以下为任何 Kotlin class 实现通用等值/哈希码的性能优化方式(使用值类和内联):

@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
    }
}

This allows you to write a equals/hashcode implementation as follows:这允许您编写一个 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 ->
            combine(lhs.title, rhs.title)
            .combineEquals { lhs.bytes contentEquals rhs.bytes }
        }
    }

    override fun hashCode(): Int {
        return Objects.hashCode(title, bytes.contentHashCode())
    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM