Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to store temporary variable while initializing a Kotlin object?

Tags:

kotlin

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

  • Class should contain two immutable integer fields: numerator and denominator.
  • Class should contain valid equals, hashCode and toString implementations.
  • When class is initialized, numerator and denominator should be deleted by their GCD (it means that Ratio(1, 2) == Ratio(2, 4 /* or 4, 8 */) or Ratio(2, 4 /* or 4, 8 */).numerator == 1, .denominator == 2 etc.)
  • This class should contain mul method that takes another Ratio and returns multiplication result of the current ratio and the given one.

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

like image 806
Alex Avatar asked Oct 19 '22 22:10

Alex


1 Answers

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.

like image 152
Alex Avatar answered Oct 22 '22 12:10

Alex