How to store a temporary variable when initializing a Kotlin object?
I am studying Kotlin and as part of the training, I want to create a class that will represent a rational number, the requirements are:
- The class must contain two immutable integer fields: the numerator and the denominator.
- The class must contain valid equals, hashCode, and toString values.
- When the class is initialized, the numerator and denominator must be removed from their GCD (meaning that
Ratio(1, 2) == Ratio(2, 4 /* or 4, 8 */) or Ratio(2, 4 /* or 4, 8 */).numerator == 1, .denominator == 2
, etc.) - This class must contain a mul method that takes another relation and returns the multiplication of the current relation and the given one.
I tried to use data classes that looked appropriate for the task, but I got stuck in not being able to define a custom constructor (both the numerator and denominator need to be removed in their GCD).
Possible Solution:
class Ratio(num : Int, denom : Int) {
val numerator = num / gcd(num, denom)
val denominator = denom / gcd(num, denom) // GCD calculated twice!
}
What is the simplest way to define a constructor for a class so that the GCD is evaluated once?
UPDATE
OK, it looks like I found a possible solution:
data class Ratio(num : Int, denom : Int) {
val numerator : Int
val denominator : Int
{
val gcd = calcGcd(num, denom)
numerator = num / gcd
denominator = denom / gcd
}
}
but it renders this data attribute useless - after this change, the Ratio class no longer has auto-generated equals / hashCode / toString.
Tested on the latest Kotlin version - 0.9.66
A program that reproduces this behavior:
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())
}
output:
r = Ratio@4ac68d3e
r2 = Ratio2(num=1, denom=6)
which is clear that Ratio no longer has an auto generated toString method
source to share
Ok, I found the answer (thanks to Andrew for pointing out the need to have a private ctor in the described use case):
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)
}
}
}
for some reason, the 'data' qualifier will be useless if the class uses initializer blocks, so if you want custom build logic and keep the automatically generated hashCode / equals / toString methods, you will need to use a factory.
source to share
What about:
class Ratio(num : Int, denom : Int) {
private val theGcd = gcd(num, denom)
val numerator = num / theGcd
val denominator = denom / theGcd
}
EDIT: Fair point regarding the useless area. An alternative could be to use a lazy evaluated object. See the docs here http://kotlinlang.org/docs/reference/delegated-properties.html
Here (unverified) goes for it.
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
}
source to share