I'm studying Kotlin and as part of learning it I want to design a class that would represent a rational number, requirements:
Ratio(1, 2) == Ratio(2, 4 /* or 4, 8 */) or Ratio(2, 4 /* or 4, 8 */).numerator == 1, .denominator == 2
etc.)I tried to use data classes what looked suitable for that task, but I was stuck with inability to define a custom constructor (both numerator and denominator need to be deleted to 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 class constructor so that GCD is calculated once?
UPDATE
OK, it looks like I found the 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 that data qualifier useless - after this change Ratio class no longer has auto generated equals/hashCode/toString.
Verified on the latest version of Kotlin - 0.9.66
Program that reproduces that 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)
that's clear that Ratio no longer has auto generated toString method
OK, I found an answer (thanks to Andrey who pointed to the necessity to have 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 'data' qualifier will be rendered useless if initializer blocks are used in the class, so if you want to have custom construction logic and retain auto generated hashCode/equals/toString methods you'll need to use factory methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With