Reputation: 3192
How can I solve the following case?
interface I
class A(i: I)
class C : I, A(this) // << --- 'this' is not defined in this context
In short, I want to pass the class instance to super class constructor.
Is it possible in Kotlin?
P.S. All the answers are good and technically correct. But let's give a concrete example:
interface Pilot {
fun informAboutObstacle()
}
abstract class Car(private val pilot: Pilot) {
fun drive() {
while (true) {
// ....
if (haveObstacleDetected()) {
pilot.informAboutObstacle()
}
// ....
}
}
fun break() {
// stop the car
}
}
class AutopilotCar : Pilot, Car(this) { // For example, Tesla :)
override fun informAboutObstacle() {
break() // stop the car
}
}
This example don't look too contrived, and why can't I implement it with OOP-friendly language?
Upvotes: 12
Views: 8723
Reputation: 47975
No, this is not possible on the JVM. this
is only available after the super class has been initialized.
From
https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.2.4
The instance initialization method (§2.9.1) for class myClass sees the new uninitialized object as its this argument in local variable 0. Before that method invokes another instance initialization method of myClass or its direct superclass on this, the only operation the method can perform on this is assigning fields declared within myClass.
So the bytecode instruction aload 0
to push this
on the stack is forbidden before the super-class constructor is called. That's why it cannot be passed as an argument to the super-constructor.
Kotlin was born as a JVM language and aims for maximum interoperability with Java code and a minimum overhead of its language features. While Kotlin could have chosen to orchestrate object initialization in a different way, it would create problems in mixed Java-Kotlin class hierarchies and add significant overhead.
Update 2025-01-11
As a preview feature in Java 22 (JEP 447), certain statements like argument validation can appear before super(...)
or this(...)
in constructors.
Example:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0) throw new IllegalArgumentException();
super(Long.toString(value));
}
}
However, access to this
or instance fields is still not allowed before calling super()
.
Upvotes: 9
Reputation: 200148
In the good tradition of OOP languages such as Java, C# or Swift, Kotlin doesn't allow you to leak the this
reference before the call to superclass initialization has completed. In your special case you're just storing the reference, but in just a slightly different case the superclass code might try to use the received object, which at that point is still uninitialized.
As a specific example of why languages don't allow this, consider a case where A
is a class from a library you use and this rule is not in effect. You pass this
like you do and things work fine. Later you update the library to a newer version and it happens to add something as benign as i.toString()
to its constructor. It has no idea it's actually calling an overridden method on itself. Your toString()
implementation observes all its invariants broken, such as uninitialized val
s.
This design suffers from other problems, not just the circular initialization dependency you are struggling with now. In a nutshell, the class A
expects this:
But instead you create this:
The class A
has a dependency on a collaborator object of type I
. It doesn't expect itself as the collaborator. This may bring about all kinds of weird bugs. For example your C.toString()
may delegate to super.toString()
and A.toString()
(A
is the super
of C
) may call into I.toString()
, resulting in a StackOverflowError
.
I can't say from your question whether A
is designed for extension, which would make the C : A
part correct, but you should definitely disentangle A
from I
.
Upvotes: 2