初始化 Kotlin 数据类的主构造函数中的属性

Initializing properties in the primary constructor of a Kotlin data class

提问人:pbuchheit 提问时间:11/17/2023 更新时间:11/17/2023 访问量:38

问:

我正在做一个需要处理“有理”数字的 Kotlin 项目。为此,我有一个类,它将有理数存储为两个整数(分子和分母),并使用这些值执行一些算术运算。我遇到的问题是,其中一个要求是将值规范化。传入值 2/4 应存储为 1/2,6/8 应存储为 1/4,依此类推。我还想把它做成一个“数据类”,这样我就可以利用内置的 equals 和 hashcode 函数。

我的类如下所示:

data class Rational(val numerator: BigInteger, val denominator: BigInteger): Comparable<Rational>
{
    init {
        if(denominator.equals(0))
        {
            throw IllegalArgumentException("0 is not a valid denominator")
        }

        //normalize the value by reducing to the least common denominator
        val gcd = numerator.gcd(denominator)
        this.numerator = numerator.div(gcd)
        this.denominator = denominator.div(gcd)

    }
}

infix fun BigInteger.divBy(d: BigInteger): Rational
{
    return Rational(this, d)
}

fun main() {

    val half = 2 divBy 4
    println(half) // expected: Rational(numerator=1, denominator=2)
}

由于参数为“val”,因此无法编译。我不想使属性可变,但我不确定在设置值之前如何进行计算。我无法删除修饰符,因为它们是数据类所必需的。

初始化在设置值之前需要处理的属性的过程是什么?到目前为止,我能找到的唯一答案是:如何在初始化 Kotlin 对象时存储临时变量? 这似乎是针对 Kotlin 的旧版本(1.0 之前的版本)。

Kotlin 构造函数 初始化

评论

0赞 Louis Wasserman 11/17/2023
您将无法在数据类的主构造函数中执行此操作。如果在辅助构造函数中执行此操作,或使类非数据,或两者兼而有之,则可以执行此操作。
0赞 pbuchheit 11/17/2023
@LouisWasserman 你知道使用辅助构造函数来做这样的事情的一个很好的例子吗?我找到的所有示例都假设静态变量:constructor(a,b):this(“1”, “2”) 或简单的内联操作:constructor(a,b): this(a+b, “ba”)
0赞 Louis Wasserman 11/17/2023
没关系。你不能用数据类来做到这一点,因为公共构造函数和所需的输入构造函数具有相同的签名。当然,也没有办法防止搞砸你的不变量。你必须从类中删除。copydata
0赞 gidds 11/17/2023
FWIW,我自己的 Rational 类(不是每个人都写其中一个吗?)只是一个普通的类。毕竟,一旦你覆盖了 、 、 、 和 may 和(都是单行),那么数据类就没有多大好处了——而且你无论如何都不想在这里。(如果你这么想,你还可以实现和、算术运算符、计算属性、用于从其他数值类型创建实例的扩展函数、扩展运算符......这是一个有趣的练习!equals()hashCode()toString()component0()component1()copy()ComparableNumber

答:

2赞 broot 11/17/2023 #1

我相信这不可能直接实现。根据设计,数据类是一个简单的数据容器。如果我们向它传递一个值,我们希望它恰好持有这个值。动态转换值可能被视为意外行为。也许提供成员函数或工厂函数会更好?normalized()

话虽如此,我们可以通过隐藏主构造函数并为伴随对象提供运算符来欺骗它:invoke()

fun main() {
    println(Rational(4.toBigInteger(), 8.toBigInteger())) // Rational(numerator=1, denominator=2)
}

data class Rational private constructor(val numerator: BigInteger, val denominator: BigInteger) {
    companion object {
        operator fun invoke(numerator: BigInteger, denominator: BigInteger): Rational {
            if(denominator.equals(0))
            {
                throw IllegalArgumentException("0 is not a valid denominator")
            }

            //normalize the value by reducing to the least common denominator
            val gcd = numerator.gcd(denominator)
            return Rational(numerator.div(gcd), denominator.div(gcd))
        }
    }
}

当然,这并不完全是你所要求的。我们在这里不使用构造函数,而使用看起来像构造函数的函数。另外,我们需要记住不进行规范化的函数,我相信我们不能覆盖它。copy()

0赞 pbuchheit 11/17/2023 #2

如果以后有人遇到这种情况,我最终更改为非数据类并自己实现 equals 和 hashmap 函数。最终结果如下所示:

class Rational ( numerator: BigInteger,  denominator: BigInteger): Comparable<Rational>
{
    private val numerator: BigInteger
    private val denominator: BigInteger
    init {
        require(denominator!=BigInteger.ZERO)
        {
            throw IllegalArgumentException("0 is not a valid denominator")
        }

        //normalize the value by reducing to the least common denominator and simplifying negation
        val sign: BigInteger = denominator.signum().toBigInteger()

        val gcd = numerator.gcd(denominator)
        this.numerator = sign * numerator.div(gcd)
        this.denominator = sign * denominator.div(gcd)
    }

    override fun toString(): String {
        if(denominator == BigInteger.valueOf(1))
        {
            return numerator.toString()
        }
        return "$numerator/$denominator"
    }

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

        other as Rational

        if (numerator != other.numerator) return false
        if (denominator != other.denominator) return false

        return true
    }

    override fun hashCode(): Int {
        var result = numerator.hashCode()
        result = 31 * result + denominator.hashCode()
        return result
    }
}