【问题标题】:sealed class's objects mysteriously becoming null when referenced by other companion object当被其他伴随对象引用时,密封类的对象神秘地变为空
【发布时间】:2017-12-05 07:59:15
【问题描述】:

有一个密封类:

sealed class Alphabet(val name: String) {
  object A : Alphabet("A")
  object B : Alphabet("B")
  object C : Alphabet("C")
  object D : Alphabet("D")
  object E : Alphabet("E")

  companion object {
    val array = arrayOf(A, B, C, D, E)
    val list = listOf(A, B, C, D, E)
  }

  override fun toString(): String {
    return name
  }
}

还有另一个类有伴生对象:

class AlphabetMap {

  companion object {
    val map = mapOf(
      Alphabet.A to 1,
      Alphabet.B to 2,
      Alphabet.C to 3,
      Alphabet.D to 4,
      Alphabet.E to 5
    )
  }
}

如果我想打印数组(或列表):

class AlphabetTest {

  @Test
  fun printValues() {
    Alphabet.array.forEach { print("$it ") };println()
    Alphabet.list.forEach { print("$it ") };println()
  }
}

它正确打印结果:

A B C D E 
A B C D E

但如果我在代码中初始化AlphabetMap

class AlphabetTest {

  val m = AlphabetMap()

  @Test
  fun printValues() {
    Alphabet.array.forEach { print("$it ") };println()
    Alphabet.list.forEach { print("$it ") };println()
  }
}

结果神秘地变成了:

null B C D E 
null B C D E 

第一个元素(A)变为空

如果我定义

val m = AlphabetMap

结果是一样的。

如果我在函数中初始化 AlphabetMap:

  @Test
  fun printValues() {
    val m = AlphabetMap()  // or val m = AlphabetMap
    Alphabet.array.forEach { print("$it ") };println()
    Alphabet.list.forEach { print("$it ") };println()
  }

结果是一样的:

null B C D E 
null B C D E 

但如果我这样初始化:

  @Test
  fun printValues() {
    Alphabet.array.forEach { print("$it ") };println()
    val m = AlphabetMap() // or val m = AlphabetMap
    Alphabet.list.forEach { print("$it ") };println()
  }

现在一切正常:

A B C D E 
A B C D E 

如果我将 AlphabetMap 重写为

class AlphabetMap {
  companion object {
    val map = mapOf(
      //Alphabet.A to 1,
      Alphabet.B to 2,
      Alphabet.C to 3,
      Alphabet.D to 4,
      Alphabet.E to 5
    )
  }
}

结果变成了

A null C D E 
A null C D E 

如果 AlphebetMap 是:

class AlphabetMap {
  companion object {
    val map = mapOf(
      Alphabet.E to 5
    )
  }
}

结果变成:

A B C D null 
A B C D null 

会出什么问题?这是一个错误吗?还是语言功能?

环境: jdk1.8.0_144 , OS X

<kotlin.version>1.2.0</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<kotlin.compiler.incremental>false</kotlin.compiler.incremental>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-stdlib</artifactId>
  <version>${kotlin.version}</version>
</dependency>

<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlinx</groupId>
  <artifactId>kotlinx-coroutines-core</artifactId>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-test-junit</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-test</artifactId>
  <scope>test</scope>
</dependency>

【问题讨论】:

  • 我怀疑这是 Java 中静态初始化顺序问题的变体。
  • @smallufo:请发布您的最新更新作为答案,它不属于问题。
  • @WilliMentzel 好的,完成。
  • @smallufo 我从您的问题 +1 +1 中删除了答案部分

标签: kotlin


【解决方案1】:

另一种解决方案是定义 array / list by lazy ,这似乎解决了这里的所有问题。

sealed class Alphabet(val name: String) {
  object A : Alphabet("A")
  object B : Alphabet("B")
  object C : Alphabet("C")
  object D : Alphabet("D")
  object E : Alphabet("E")

  companion object {
    val array: Array<Alphabet> by lazy {
      arrayOf(A, B, C, D, E)
    }
    val list: List<Alphabet> by lazy {
      listOf(A , B , C , D , E)
    }
  }

  override fun toString(): String {
    return name
  }
}

现在效果很好。

A B C D E 
A B C D E 

【讨论】:

  • 这真的让我免于很多头痛
  • @smallufo 你能解释一下问题是什么以及它是如何解决的吗?
【解决方案2】:

解决这个问题的唯一方法是将地图定义移到类之外:

val map = mapOf(
  Alphabet.A to 1,
  Alphabet.B to 2,
  Alphabet.C to 3,
  Alphabet.D to 4,
  Alphabet.E to 5
)

class AlphabetMap {
  companion object {

  }
}

根据 JetBrain 的 reply on youtrackreddit ,这是 As Design ,没有办法解决。

初始化中的循环问题没有通用的解决方案。不 不管我们想出哪些确切的规则,如果 对象 A 访问对象 B 并且对象 B 的初始化程序访问 对象 A,其中一个将能够观察另一个 未初始化状态。

【讨论】:

    【解决方案3】:

    正确的解决方案是打破自引用链,因为代码不可能正确初始化。您可以选择在哪里执行此操作。

    您可以通过以下方式做到这一点:

    1. 更改某些对象的定义,使它们不在同一个自引用对象中

    2. 引入可以存在于自引用对象之前的中间持有者对象以隐式地打破链条。正如上面回答的那样,lazy 就是这样做的。

    在编写代码时,您可能会在许多情况下和库中遇到此问题。这只是静态实例化的另一种情况。请记住 kotlin 在第一个对象引用时实例化,因此您可以通过隐藏在对象引用甚至 lambdas 后面来推迟实例化。

    【讨论】:

      猜你喜欢
      • 2020-02-29
      • 1970-01-01
      • 1970-01-01
      • 2013-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-21
      相关资源
      最近更新 更多