Friday 23 September 2022

Kotlin: abstract classes, backing fields/properties, lateinit, and frustration

G'day:

I'm just continuing on in the Kotlin "Classes" docs, carrying on from my earlier article "Kotlin: investigating classes".


Abstract classes

I have pretty much run with the example in the docs just to see it in action. I don't wanna copy and paste stuff cos I don't learn so well just by looking at the code. Making myself remember what I need to type - even if it's just class syntax with a method or two - helps me learn it.

The thing the example had was to have an abstract class that has a concrete implementation of a method, extended by another abstract class that actually makes that method abstract again, followed by a concrete implementation class. To get my brain around that, I wrote it in Java first (all this experimentation is making my Java better too btw: I'm writing more Java-comparison code for these articles than I'm necessarily showing in them). My Java is limited to one-off class files with a main method though. I can't be arsed setting up a Java project in IntelliJ and working out how to get JUnit working, etc. Sorry. (Dammit I feel guilty now). Anyway, Java version:

public class TestAbstract {
    public static void main(String[] args){
        MyWildShape mws = new MyWildShape();
        mws.draw();
    }
}

abstract class Shape {
    public void draw() {
        System.out.println("Shape.draw"); // MyWildShape.draw
    }
}


abstract class WildShape extends Shape {
    abstract public void draw();
}  


class MyWildShape extends WildShape {
    @Override
    public void draw() {
        System.out.println("MyWildShape.draw");
    }
}

And the Kotlin version:

abstract class Shape {
    open fun draw() = print("Shape.draw")
}

abstract class WildShape : Shape() {
    abstract override fun draw()
}

class MyWildShape : WildShape() {
     override fun draw() = print("MyWildShape.draw")
}

class AbstractTest : DescribeSpec({
    describe("abstract test") {
        it("permits concrete subclasses to override abstract methods that are not overridden in the base class") {
            val mws = MyWildShape()
            val output = SystemLambda.tapSystemOut {
                mws.draw()
            }
            output shouldBe "MyWildShape.draw"
        }
    }
})

I wondered what the need for open was in that. Kotlin's been pretty good an letting boilerplate be inferrable, and this seemed like boilerplate. I found the answer in the docs:

By default, Kotlin classes are final – they can't be inherited. To make a class inheritable, mark it with the open keyword

Kotlin / Concepts / Classes and objects / Inheritance

As to why that's the case, I had to dig slightly deeper, but I found a discussion on it here: Classes final by default, and at the top of that discussion someone links to the book "Effective Java" wherein I guess someone recommends making stuff final by default. I'm not entirely convinced with this as it kinda presupposes all code is written in a specific way: implemented to an interface, and nothing every expects a concrete implementation, basically. I'm OK writing my code like that, however there's no guarantee the author of some lib I need to use also has. And in my experience (granted: in PHP) it's not the case. But so be it. I guess it does encourage one to stop and think. And design to interfaces. Which is a good thing.


Companion objects

I messed with these in a coupla other articles:

I've nothing else I feel I need to check-out on these right now.


Back to backing fields (and backing properties)

Fields

In an earlier article ("Kotlin: there's no such thing as static, apparently"), I learned that a property has a backing field: the underlying variable that stores the value. One just refers to it as field in accessor implementations:

class Person {
    var firstName = ""
        get() = field
        set(value) {
            field = value
        }
    var lastName = ""
        get() = field
        set(value) {
            field = value
        }
}

Under the hood Kotlin is creating appropriately-named discrete private variables for each property's field.

One cool thing IntelliJ can do is to show the bytecode that the Kotlin code would generate (Tools › Kotlin › Show Kotlin Byte Code), and from there one can decompile that back to Java. The result in this case being (I've trimmed some irrelevant stuff):

final class Person {
   private String firstName = "";
   private String lastName = "";

   public final String getFirstName() {
      return this.firstName;
   }

   public final void setFirstName(String value) {
      this.firstName = value;
   }

   public final String getLastName() {
      return this.lastName;
   }

   public final void setLastName(String value) {
      this.lastName = value;
   }
}

One thing I wondered about before when I found myself getting a stack overflow when doing this:

var firstName = ""
    get() = field
    set(value) {
    	// was field = value
        firstName = value
    }

Whether this was because my reference to firstName there was actually calling "itself". It turns out it is, and IntelliJ was even telling me:


Backing properties

This was the next thing that threw me. The docs also talk about backing properties, and really didn't make it at all clear why these existed.

If you want to do something that does not fit into this implicit backing field scheme, you can always fall back to having a backing property

They give an example, and I could understand the code, but it didn't really sink in as to what specifically showing me. I did a google and was pleased to find out I was not the only person a bit flumoxed by all this, and found a good answer here: I have a hard time understanding the purpose of a "backing property". And I was able to contrive a test case to demonstrate it (largely to myself) in action:

class MyList {
    private var _myList = mutableListOf<String>()
    val immutable: List<String>
        get() = _myList
    var mutable: MutableList<String> = mutableListOf()
        set(value) {
            field = value
            _myList = value
        }
}

There's really no trick to it. It's just when an accessor method needs to store its value in some other variable, not its backing field. In the example above I have a class that has a property called mutable that has a setter which will take a MutableList of strings, and another property called immutable which actually accesses the same underlying variable, but returns it as an immutable List. This is a daft example, but it demonstrates the point. I've two properties, and they both access the same underlying data, so it has to be in a specific private property rather than using field which intrinsically is a different variable for each property. Also note I am setting mutable's backing field too. Not for any reason, just to show I can.

Of course I wrote a test for this:

class BackingPropertiesTest : DescribeSpec({
    it("should take a MutableList and return an ImmutableList") {
        val mutableList = mutableListOf("tahi", "rua", "toru")
        val myList = MyList()
        myList.mutable = mutableList

        myList.immutable.shouldBeInstanceOf<List<String>>()
        myList.immutable shouldBe listOf("tahi", "rua", "toru")

        mutableList.add("wha")
        myList.immutable.shouldBeInstanceOf<List<String>>()
        myList.immutable shouldBe listOf("tahi", "rua", "toru", "wha")
    }
})

Straight forward stuff: just demonstrating what the code is supposed to be doing. One thing I thought was neat was how I could append that new element to the mutable list, and when I accessed it again, I got a new immutable list out, also with my new value. It almost seems like I am changing the immutable list, but of course it's a new list each time.


lateinit

This is the solution to a problem that I had to work around the other day. In the end I was "just doing it wrong" for the given situation (I forget what it was now), but I had thought "but I don't want to initalise that with a non-null value now just to satisfy the compiler. I'll deal with it later. Trust me". And of course the "trust me" part is implemented via the lateinit modifier.

class Colour(val en: String) {
    lateinit var mi: String

    fun setMaori(value: String) {
      mi = value
    }
}

Here I have then English version of the colour as a normal property handled by the primary constructor, and another property mi representing the Maori word for the colour, and that isn't handled by any constructor, and is also not initialised. It's happy to stay undefined until "later". If I didn't have the lateinit there, the code would not even compile:

e: [...]\LateInitPropertiesTest.kt: (52, 5): Property must be initialized or be abstract

IntelliJ makes it clear too:

I have a coupla tests demonstrating this in action:

it("allows me to create a colour object without an mi property") {
    val red  = Colour("red")
    red.en shouldBe "red"
}

it("lets me set the mi property later") {
    val orange  = Colour("orange")
    orange.setMaori("karaka")

    orange.mi shouldBe "karaka"
}

What if I try to read that property before it's been initialised?

it("throws UninitializedPropertyAccessException if access mi before it's been set") {
    val yellow  = Colour("yellow")

    val exception = shouldThrow<UninitializedPropertyAccessException> {
        yellow.mi shouldNotBe "kōwhai"
    }
    exception.message shouldBe "lateinit property mi has not been initialized"
}

It throws an exception at runtime. Cool. And it's a specific exception, with a clear message.

For good measure I also checked with a method call with the reference to mi within the class. I did not expect any different behaviour, but I like to check these things.

class Colour(val en: String) {
    lateinit var mi: String

    fun setMaori(value: String) {
      mi = value
    }

    fun getMaori() = mi
}
it("throws UninitializedPropertyAccessException if use mi before it's been set") {
    val green  = Colour("green")

    val exception = shouldThrow<UninitializedPropertyAccessException> {
        green.getMaori() shouldNotBe "kakariki"
    }
    exception.message shouldBe "lateinit property mi has not been initialized"
}

One can also check to see if the property is initialised before using it:

class Colour(val en: String) {
    lateinit var mi: String

    fun setMaori(value: String) {
      mi = value
    }

    fun getMaori() = mi

    fun isMiInitialized() = ::mi.isInitialized
}
it("can have initialisation status checked") {
    val green  = Colour("green")

    green.isMiInitialized() shouldBe false
    green.setMaori("kakariki")
    green.isMiInitialized() shouldBe true
}

I thought I might be able to go green::mi.isInitialized, but I get told off:

Looking in the docs, this is because:

This check is only available for properties that are lexically accessible when declared in the same type, in one of the outer types, or at top level in the same file.

Ooookay. I cannot actually work out how to write code where I can call isInitialized where the property is "declared… in one of the outer types, or at top level in the same file". I always get "Backing field of 'var someValue: String' is not accessible at this point". And that is a coupla hours or so of my life I am never going to get back.


And as that was a bit frustrating at the end there, I'm gonna go and do something else now. You need to picture me with my bottom lip jutted-out in a put-upon way when I say that. I tell you: if I had a ball, I would take it and go home ;-)

Righto.

--
Adam