I'm seeing something really weird. If I declare a...
# announcements
j
I'm seeing something really weird. If I declare a var and set it to a declared constant value to initialize it, when I change it (in a overriden method called by the superclass's constructor) I see the value change, yet when I access the value later it seems to have been reset back to the constant. companion object { private const val DEFAULT_MAX_COLLAPSED_LINES = 3 } private var collapsedMaxLines: Int = DEFAULT_MAX_COLLAPSED_LINES ... In method called in constructor: collapsedMaxLines = 5 ... when I later access collapsedMaxLines it's 3 again. If I initialize to a number like 0, it gets the change, but when I used the declared constant in the companion (edit: or any non-zero value it appears), it seems to always revert back to the constant - is this a bug?
o
Can you provide a complete sample program exhibiting this behavior?
j
Edited to be concise :
Copy code
public class MyBaseClass  {
    public MyBaseClass() {}

    public MyBaseClass(Map<String, String> attrs) {
        processAttribute(attrs);
    }

    protected void processAttribute(Map<String, String> attrs) {
    }
}
Copy code
class MyKotlinClass @JvmOverloads constructor(attrs: Map<String, String>? = null) :
    MyBaseClass(attrs) {
    companion object {
        const val COLLAPSED_LINES = "CollapsedLines"
        const val DEFAULT_COLLAPSED_LINES = 3
    }

    private var collapsedMaxLines = DEFAULT_COLLAPSED_LINES

    override fun processAttribute(attrs: Map<String, String>) {
        super.processAttribute(attrs)
        processExpandableAttrs(attrs)
    }

    fun getCurrentCollapsedLines() = collapsedMaxLines

    private fun processExpandableAttrs(attrs: Map<String, String>) {
        collapsedMaxLines = attrs[COLLAPSED_LINES]?.toInt() ?: DEFAULT_COLLAPSED_LINES
    }
}
Copy code
val myClass = MyKotlinClass(mapOf(MyKotlinClass.COLLAPSED_LINES to "5"))
        var maxLines = myClass.getCurrentCollapsedLines()
Where MyBaseClass's constructor calls processAttribute. If I set breakpoints I see the value change from 3 (DEFAULT_COLLAPSED_LINES) to the value I set in the attributes, but when I later access it - it's back to 3. If I change the variable declaration to use a hardcoded number
private var collapsedMaxLines: Int = 0
(instead of the companion object constant) , if I set a breakpoint, again the number gets assigned to the attribute I pass in, and stays assigned when I access it later.
Ok this is even more bizarre. When I initialize the value to 0 everything works as expected, but if I initialize to any other number the reversion takes place - so it doesn't appear to matter that the number being initialized was a companion object value, just any non-zero value? Seems like logically the initialization to a value should take place before I can assign that value in processExpandableAttrs. Even more confusing is why the behavior is different based on the initial value unless there is some special treatment for a 0 init value.
o
This is a well known issue (in any language that has it) with virtual call in constructor of base class. Initialization order is at play. When
MyKotlinClass
is created, it first calls
super
constructor, and you have a call to
processExpandableAttrs
before initialization for
MyKotlinClass
continues. It will be the same in you write this class in Java. Zero is the default value for the
Int
type, so compiler doesn’t put a write to
collapsedMaxLines
backing field into `MyKotlinClass`’s constructor.
You can put a breakpoint in
processExpandableAttrs
and on the
collapsedMaxLines
and see yourself what goes on.
j
Thanks for the explanation about initiation order - very helpful in understanding the behavior here. Actually the behavior is not the same in Java - you are right that the value does get wiped out (initialization order) - but if you initialize to 0 in java it returns 0 not 5 (consistent initialization - regardless or 0 or non-zero) whereas kotlin behaves differently (initializes one way for 0 and differently for non-zero).