Joe Lapp
Joe Lapp

Reputation: 2975

Transforming child constructor params for parent constructor in Kotlin

I'm a Java veteran who's learning Kotlin. I'm finding myself a bit mystified about how take constructor parameters provided to a child class and transform them to the parameters required by the parent class. The problem arises when the parameters provided to the child are not suitable for the parent.

For example, when I have IntelliJ IDEA convert the following to Kotlin...

class Base
{
    final int w;
    final int h;

    Base(int w, int h)
    {
        this.w = w;
        this.h = h;
    }
}

class Derived extends Base
{
    Derived(int x)
    {
        // some complex derivation
        Converter c = new Converter(x);
        super(c.a, c.b);
    }
}

class Converter
{
    final int a;
    final int b;

    Converter(int x)
    {
        a = x + 2;
        b = x - 2;
    }
}

I get the following, including an error at the indicated position saying that parameters were not passed in for w and h...

open class Base(val w: Int, val h: Int)

class Derived(x: Int) : Base() {
    //                       ^ error
    init {
        // some complex derivation
        val c = Converter(x)
        // How do I provide c.a and c.b?
    }
}

class Converter(x: Int) {
    val a: Int
    val b: Int

    init {
        a = x + 2
        b = x - 2
    }
}

What's the general solution to this problem? (Clearly, I'm not doing anything as simple as shown here. I simplified just to present the problem.)

Upvotes: 0

Views: 1885

Answers (2)

Joe Lapp
Joe Lapp

Reputation: 2975

The comment by JB Nizet above set me on the right track: you can't do it this way in Java either. I've been tying myself in knots trying to do things the Kotlin way. In this case, I'm trying to use properties everywhere I would use accessor methods in Java.

One solution is to make the base properties abstract:

abstract class Base {
    abstract val w: Int
    abstract val h: Int

    // base implementation
}

class Derived(x: Int) : Base() {
    private val c = Converter(x)

    override val w: Int
        get() = c.a

    override val h: Int
        get() = c.b
}

class Converter(x: Int) {
    // implementation not relevant here
    val a = x + 2
    val b = x - 2
}

Upvotes: 0

gidds
gidds

Reputation: 18617

I'd suggest using a simpler, private constructor, and adding a factory method to do the conversion, e.g.:

class Derived private constructor(val w: Int, val h: Int) : Base(w, h) {
    companion object {
        operator fun invoke(x: Int): Derived {
            // Some complex derivation…
            val c = Converter(x)
            return Derived(c.a, c.b)
        }
    }
}

This can be called in exactly the same way as a constructor, e.g. val d = Derived(1), but it has several advantages:

  • It can do lots of processing before calling the constructor.

  • It can return cached values instead of new instances, where appropriate.

  • It can return a subclass.  (So Derived could be an abstract class — or you might not even need it at all.)  The exact class can differ between calls, and can even be an anonymous type.

  • It can have a name.  This is especially important if you need multiple methods taking the same parameters.  (E.g. a Point object which could be constructed from rectangular or polar co-ordinates.)  However, a factory method doesn't need a specific name; if you implement the invoke() method in the companion object (as above), you can call it in exactly the same way as a constructor.

  • It makes it easier to change the implementation of the class without affecting its public interface.

There's one disadvantage, though:

  • It can't be used by subclass constructors.

Unlike the property-based answer, this doesn't require any changes to the rest of the class, and doesn't keep the Convertor object around; it's purely a different way of calling the constructor.

(In some cases, you might not need to make the primary constructor private; it could provide an alternative to the factory method(s), as long as the signatures don't match.)

Upvotes: 2

Related Questions