4
votes

Let's say I want to create sealed class, filled with some objects. Then I want to create list of all such objects, so I create list in companion object:

fun main() {
    println(Color.Blue)
    println(Color.allColors)
}

sealed class Color {
    object Red : Color();
    object Blue : Color();

    companion object {
        val allColors = listOf(
                Red,
                Blue
        )
    }
}

However, issue with above code is that when calling Color.Blue directly for the first time, companion object is initialized before Blue and thus resulting list contains [Red, null]. This is doubly problematic because Kotlin assumes list contains non-null values.

I know above example is simple enough that I could replace sealed class with enum, but this is just a simplified example. In many cases it is beneficial to use sealed classes over enums (for example when you need to add type parameters to individual objects).

What would be the best way to solve that issue with least amount of boilerplate and allocating objects? I've come up with two workarounds, but I do not like either of them:

Lazy

fun main() {
    println(Color.Blue)
    println(Color.allColors)
}

sealed class Color {
    object Red : Color();
    object Blue : Color();

    companion object {
        val allColors by lazy {
            listOf(
                    Red,
                    Blue
            )
        }
    }
}

Above solution looks fine and does not cause much boiler plate, but it creates one additional object that lives forever for every property in the companion object. I would also need to repeat lazy keyword on any additional properties.

Moving initialization into another object

fun main() {
    println(Color.Blue)
    println(Color.allColors)
}

sealed class Color {
    object Red : Color();
    object Blue : Color();

    private object Initializer {
        val allColors = listOf(
                Red,
                Blue
        )
    }

    companion object {
        val allColors: List<Color>
            get() = Initializer.allColors
    }
}

This method has a benefit of only creating one object for all properties in companion object, but it creates a lot of additional boilerplate.

Is there a better way to achieve this?

EDIT: There is an issue on Kotlin issue tracker for this case: https://youtrack.jetbrains.com/issue/KT-8970

1
You can make it as a fun allColors() - Demigod
Yes, but that will create list on every call. - Matej Drobnič
Yeah, it's just you said that by lazy not the best approach also, cause of additional object? So you don't mind to keep this val, but do not want to have a lazy initializer? - Demigod
I agree, all solution are not ideals. I just want to make sure there wasn't any clever hack to achieve this. - Matej Drobnič
You can also call Color.allColors at the top of your Color class's file. Is there some open issue related to this behavior? - Raymond Arteaga

1 Answers

0
votes
sealed class Color(var meh:Int) {
    object Red : Color(10)
    object Blue : Color(20)

    companion object {
        private var colorsList:List<Color>? = null
        val allColors:List<Color>
            get() = colorsList?:run{
                colorsList = listOf(
                        Red,
                        Blue
                )
                colorsList!!
            }
    }
}

This is the singleton of always. It's just another way to do it. But the Initializer object looks cleaner.