Wednesday 14 July 2021

Android Java to Kotlin migration by example part 7: companion object problem

 In the previous part we've seen how to use companion objects in Kotlin, giving us something similar to Java/C# static fields and methods.

But there is a problem with it, when using inheritance: companion objects are not accessible from the subclass.

To understand what I mean, look at this Java code:

Listing 1 (Java)

class SuperClass{
static int field = 100;

public static void classMethod(){
System.out.println(
"superclass static method called, field = "
+ field);
}
}
class SubClass extends SuperClass{

public static void subclassMethod(){
field--;
System.out.println("Field value decremented: "
+ field);
}
}

public class MainClass {
public static void main(String[] args){
SuperClass superObject = new SuperClass();
SuperClass.classMethod();

SubClass subObject = new SubClass();
SubClass.subclassMethod();
SubClass.classMethod();
}
}

Code execution result is:

Listing 2 (result)

superclass static method called, field = 100
Field value decremented: 99
superclass static method called, field = 99

It has a superclass, a subclass, both with one static method, the superclass with one static field.

In main method we can instantiate them, and:

- for the superclass instance we can call the superclass static method.

- for the subclass instance we can call both the superclass and the subclass static method.

This makes sense - the subclass instance is the superclass instance. This is how inherintance works, right?

So take a look at this Kotlin code:

Listing 3 (Kotlin)

package com.mypackage


open class SuperClassWithCompanion{
companion object CompanionObj{
var field: Int = 100;
fun classMethod(){
print("superclass companion method called, field = "
+field)
}
}
}
class SubClass: SuperClassWithCompanion(){
companion object CompanionObj {
fun subclassMethod() {
field--;
println("Field value decremented: "
+ field)
}
}
}

fun main() {

val superObject = SuperClassWithCompanion()
SuperClassWithCompanion.classMethod()

val subObject = SubClass()
SubClass.subclassMethod()
//SubClass.classMethod()
}

It's basically the same as the code in Listing 1, we're just using companion objects instead of static fields and methods. So we create a superclass instance, call its static method, the subclass instance, call it's static method (subclassMethod()) and then...

We can't call the classMethod from the subclass. It won't compile, giving the Unresolved reference: classMethod error.

And here is the problem: the companion object is not inheritable. We could say accompanies a particular class, not it's subclass.

The problem, although can be surprising; is - in fact - is not very complicated to solve. As the methods and fields of the companion object are common to all instances, we can just call SuperClassWithCompanion.classMethod() whenever we want to call SubClass.classMethod(). What's more, we can simply add such method (the same name and prototype) in the subclass and call the superclass method there:

Listing 4 (Kotlin)

package com.mypackage


open class SuperClassWithCompanion{
companion object CompanionObj{
var field: Int = 100;
fun classMethod(){
println("superclass companion method called, field = "
+field)
}
}
}
class SubClass: SuperClassWithCompanion(){
companion object CompanionObj {
fun subclassMethod() {
field--;
println("Field value decremented: "
+ field)
}
fun classMethod(){
SuperClassWithCompanion.classMethod()
}
}
}

fun main() {

val superObject = SuperClassWithCompanion()
SuperClassWithCompanion.classMethod()

val subObject = SubClass()
SubClass.subclassMethod()
SubClass.classMethod()
}

Then it works the same as our code from Listing 1:

Listing 5 (result)

superclass static method called, field = 100
Field value decremented: 99
superclass static method called, field = 99

Still, there seems to be some redundancy here, as we have to "manually" call the companion object's methods in the subclass.

The question is - does it make sense? Well, sort of. Tke Kotlin designers purposefully made it impossible to inherit the companion object: because mixing inheritance and static code in Java often led to confusion and errors.

As far as I can see, it doesn't prevent us from doing what we want - maybe just forcing to do it in more explicit way.

 

No comments:

Post a Comment

Python crash course part 10: inheritance and polymorphism

In the last part we've shown how to create and use a class in Python. Today we're going to talk about inheritance: wchich means cre...