【问题标题】:How to store temporary variable while initializing a Kotlin object?如何在初始化 Kotlin 对象时存储临时变量?
【发布时间】:2014-11-11 21:19:06
【问题描述】:

我正在学习 Kotlin,作为学习的一部分,我想设计一个代表有理数的类,要求:

  • 类应包含两个不可变整数字段:分子和分母。
  • 类应包含有效的 equals、hashCode 和 toString 实现。
  • 类初始化时,分子和分母应被其GCD删除(即Ratio(1, 2) == Ratio(2, 4 /* or 4, 8 */) or Ratio(2, 4 /* or 4, 8 */).numerator == 1, .denominator == 2等)
  • 此类应包含 mul 方法,该方法采用另一个比率并返回当前比率与给定比率的乘法结果。

我尝试使用看起来适合该任务的数据类,但我无法定义自定义构造函数(分子和分母都需要删除到它们的 GCD)。

可能的解决方案:

class Ratio(num : Int, denom : Int) {
    val numerator = num / gcd(num, denom)
    val denominator = denom / gcd(num, denom) // GCD calculated twice!
}

定义类构造函数以便计算一次 GCD 的最简单方法是什么?

更新

好的,看来我找到了可能的解决方案:

data class Ratio(num : Int, denom : Int) {
  val numerator : Int
  val denominator : Int

  {
    val gcd = calcGcd(num, denom)
    numerator = num / gcd
    denominator = denom / gcd
  }
}

但它使数据限定符变得无用 - 在此更改之后,Ratio 类不再自动生成 equals/hashCode/toString。

在最新版本的 Kotlin - 0.9.66 上验证

重现该行为的程序:

data class Ratio(num : Int, denom : Int) {
  val numerator : Int
  val denominator : Int

  {
    val gcd = BigInteger.valueOf(num.toLong()).gcd(BigInteger.valueOf(denom.toLong())).intValue();
    numerator = num / gcd;
    denominator = denom / gcd
  }
}

data class Ratio2(val num : Int, val denom : Int)

fun main(args: Array<String>) {
  println("r = " + Ratio(1, 6).toString())
  println("r2 = " + Ratio2(1, 6).toString())
}

输出:

r = Ratio@4ac68d3e
r2 = Ratio2(num=1, denom=6)

很明显,Ratio 不再具有自动生成的 toString 方法

【问题讨论】:

  • 您是否向 JetBrains 报告了无用的 data 问题?听起来像一个错误

标签: kotlin


【解决方案1】:

好的,我找到了答案(感谢 Andrey,他指出在所描述的用例中必须有私有 ctor):

data class Ratio private (val numerator : Int, val denominator : Int) {
  class object {
    fun create(numerator : Int, denominator : Int) : Ratio {
      val gcd = BigInteger.valueOf(numerator.toLong()).gcd(BigInteger.valueOf(denominator.toLong())).intValue();
      return Ratio(numerator / gcd, denominator / gcd)
    }
  }
}

由于某种原因,如果在类中使用初始化块,'data' 限定符将变得无用,因此如果您想要自定义构造逻辑并保留自动生成的 hashCode/equals/toString 方法,则需要使用工厂方法.

【讨论】:

  • 对这个概念的一些改进:kotlin-demo.jetbrains.com/…
  • 谢谢!我错过了一个事实,如果你想拥有自定义 ctor 逻辑,你可以使用调用。
  • 另外,一定要定义copy,否则会有办法破坏你的不变量
【解决方案2】:

怎么样:

class Ratio(num : Int, denom : Int) {
 private val theGcd = gcd(num, denom)
 val numerator = num / theGcd
 val denominator = denom / theGcd
}

编辑:关于无用字段的公平点。另一种方法是使用惰性求值属性。请参阅此处的文档http://kotlinlang.org/docs/reference/delegated-properties.html

这是一个(未经测试的)尝试。

import kotlin.properties.Delegates

class Ratio(num : Int, denom : Int) {
 private val theGcd: Int by Delegates.lazy {
    gcd(num, denom) 
 }

 val numerator = num / theGcd
 val denominator = denom / theGcd
}

【讨论】:

  • 不,这很糟糕——这会永久地为类实例添加冗余值——虽然这对于玩具样本来说可能没问题,但如果类布局变得更大,它将消耗比实例实际需要的更多的内存拥有。
  • 如果你唯一的问题是额外的字段,很容易摆脱: class Ratio(num : Int, denom : Int) { val numerator: Int val denominator: Int { val theGcd = gcd(num, denom) numerator = num / theGcd denominator = denom / theGcd } } (gist.github.com/abreslav/173c32a30f9f94e1cd9a) 如果你想要的是自动生成的 equals/hashCode 等,你需要一个私有构造函数和一个工厂方法
  • 谢谢安德烈。请看我上面的编辑 - 我想使用“数据”限定符,在初始化块中消除临时变量后,我发现“数据”限定符变得无用。
  • @Andrey - 看起来我在看到您的最新更新之前已经回答了 :) 感谢您如此迅速地回答我的问题。我会试试的。然而,让我完全惊讶的是,在添加初始化块后,数据限定符的效果消失了:) 当然,你应该有某种 Kotlin 语言规范(类似于 Java 的 JLS),其中所有关于语言核心的细节都将解释一下。
  • lazy 属性当然还是占用一个字段。
猜你喜欢
  • 2013-07-04
  • 1970-01-01
  • 2019-05-17
  • 2020-03-26
  • 1970-01-01
  • 2020-04-26
  • 2019-08-28
  • 1970-01-01
  • 2013-06-26
相关资源
最近更新 更多