Alex
Alex

Reputation: 3076

How to store temporary variable while initializing a Kotlin object?

I'm studying Kotlin and as part of learning it I want to design a class that would represent a rational number, requirements:

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

Upvotes: 5

Views: 3467

Answers (2)

Ben Clayton
Ben Clayton

Reputation: 82209

How about:

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

EDIT: fair point about the useless field. An alternative could be to use a lazy-evaluated property. See the docs here http://kotlinlang.org/docs/reference/delegated-properties.html

Here's an (untested) go at this..

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
}

Upvotes: 0

Alex
Alex

Reputation: 3076

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.

Upvotes: 3

Related Questions