Reputation: 2975
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
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
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:
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