【问题标题】:Initializing companion object after inner objects在内部对象之后初始化伴随对象
【发布时间】:2019-07-23 06:27:02
【问题描述】:

假设我想创建密封类,填充一些对象。然后我想创建所有这些对象的列表,所以我在伴生对象中创建列表:

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

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

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

但是,上面代码的问题是,当第一次直接调用Color.Blue 时,伴随对象在Blue 之前初始化,因此结果列表包含[Red, null]。这是双重问题,因为 Kotlin 假定列表包含非空值。

我知道上面的例子很简单,我可以用enum 替换sealed class,但这只是一个简化的例子。在许多情况下,使用密封类而不是枚举是有益的(例如,当您需要向单个对象添加类型参数时)。

用最少的样板和分配对象来解决该问题的最佳方法是什么?我想出了两个解决方法,但我不喜欢其中任何一个:

懒惰

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
            )
        }
    }
}

上面的解决方案看起来不错并且不会产生太多的样板,但它会创建一个额外的对象,该对象会为伴随对象中的每个属性永久存在。我还需要在任何其他属性上重复惰性关键字。

将初始化移到另一个对象中

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
    }
}

这种方法的好处是只为伴生对象中的所有属性创建一个对象,但它创建了很多额外的样板。

有没有更好的方法来实现这一点?

编辑:对于这种情况,Kotlin 问题跟踪器存在问题:https://youtrack.jetbrains.com/issue/KT-8970

【问题讨论】:

  • 您可以将其设为fun allColors()
  • 是的,但这会在每次通话时创建列表。
  • 是的,只是你说by lazy 也不是最好的方法,原因是additional object?所以你不介意保留这个val,但不想拥有lazy 初始化器?
  • 我同意,所有解决方案都不是理想的。我只是想确保没有任何巧妙的技巧可以实现这一目标。
  • 您也可以在 Color 类文件的顶部调用 Color.allColors。是否存在与此行为相关的未解决问题?

标签: kotlin initialization-order sealed-class


【解决方案1】:
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!!
            }
    }
}

这是always的单例。这只是另一种方法。但是 Initializer 对象看起来更干净。

【讨论】:

  • 懒惰的解决方案基本上是这样,但样板更少?
  • 事实上,有很多方法可以解决这个问题,但问题中已经列出了 3 个更简单的方法。我只是添加了另一种多余的方法来做到这一点。我真的认为kotlin最后在主类之外初始化引用对象的行为有点危险。是否存在与此行为相关的未解决问题?
猜你喜欢
  • 1970-01-01
  • 2021-11-23
  • 2013-04-05
  • 1970-01-01
  • 1970-01-01
  • 2017-03-13
  • 2014-11-20
  • 2013-06-27
  • 2019-12-29
相关资源
最近更新 更多