【问题标题】:Kotlin: Mutual assignmentsKotlin:相互分配
【发布时间】:2018-11-20 19:02:12
【问题描述】:

我想设置两个相互保持不可变引用的值。示例:

data class Person(val other: Person)
val jack = Person(jill), jill = Person(jack)   // doesn't compile

注意:lateinit 似乎不适用于数据类主构造函数。

有什么想法吗?

【问题讨论】:

  • 也许可以为空的变量?
  • 我真的希望避免可以为空的变量。应用程序的其余部分应该认为这个属性是不可变的,一旦设置。
  • 这是一个循环依赖问题:要创建jack,我需要jill,而jill又需要jack等等。
  • @m0skit0 正确。有什么想法吗?
  • @CSJ 我想我没有正确解释自己。你想要做的是逻辑上不可能(从技术上讲,它需要无限的时间),除非你想放松你的条件。

标签: kotlin immutability


【解决方案1】:

你可以摆脱这样的事情:

class Person() {
    private var _other: Person? = null

    private constructor(_other: Person? = null) : this() {
        this._other = _other
    }

    val other: Person
        get() {
            if (_other == null) {
                _other = Person(this)
            }
            return _other ?: throw AssertionError("Set to null by another thread")
        }
}

然后你就可以做到:

val jack = Person()
val jill = jack.other

由于多种原因,在此处使用data class 不起作用:

  1. 首先因为data class 不能有一个空的构造函数。

  2. 即使这不是问题,生成的方法最终也会有一个循环依赖,并且会在运行时以java.lang.StackOverflowError 失败。所以你必须覆盖toStringequals 等,这违背了使用data class 的目的

【讨论】:

  • 可能是lazylateinit 的用例?
  • 我在这里使用lateinit 重写了它:gist.github.com/jivimberg/3fd70a9bd69632c2275d14e24c0e7baa。虽然我可以将Person? 类型转换为Person,但我发现它不再具有可读性。更重要的是,我不确定当有并发访问时 isInitialized 检查会发生什么。
【解决方案2】:

这是诀窍(注意,这确实是一个技巧,你需要有充分的理由在实际代码中使用它)。

不幸的是,它不适用于数据类,因为它们似乎可以防止此类黑客攻击。

但是如果你有 java-stile 类,你可以利用两件事来获得优势:

  1. 可以在构造函数中初始化vals(与java中的final相同)
  2. 您可以在构造函数中访问this(如果您真的需要,可以将其泄露到外面)

这意味着您可以在第一个人的构造函数内部创建另一个Person,并在构造函数完成之前完成这两个类的创建。

再一次:像我在下面所做的那样暴露this 是一个的想法。当otherFactory 被调用时,它的参数只是初始化了一半。这可能会导致严重的错误,尤其是当您尝试在多线程环境中发布此类参考时。

更安全一点的方法是在第一个 Person 的构造函数中创建两个 Person(您需要提供两个实体的字段作为参数)。它更安全,因为您可以控制使用半初始化 this 引用的代码。

class Person {
    val name: String
    val other: Person

    constructor(name: String, other: Person) {
        this.name = name
        this.other = other
    }

    // !! not very safe !!
    constructor(name: String, otherFactory: (Person) -> Person) {
        this.other = otherFactory(this)
        this.name = name
    }

    // a bit safer
    constructor(name: String, otherName: String) {
        this.other = Person(otherName, this)
        this.name = name
    }
}

val person1 = Person("first") {
    Person("second", it)
}

val person2 = person1.other

print(person1.name) // first
print(person2.name) // second

val person3 = Person("third", "fourth")
val person4 = person3.other

print(person3.name)
print(person4.name)

【讨论】:

    【解决方案3】:

    感谢大家的建议。我想出了一个替代方案,想听听您的见解:

    open class Person {
      open val other: Person by lazy { Person2(this) }
      class Person2(override val other: Person): Person()
    }
    
    val jack = Person()
    val jill = jack.other
    

    这里我们让一个人根据需要懒惰地实例化另一个人,使用一个以不同方式实现other的内部子类(即,它只是在其构造函数中直接给出)。

    欢迎提出想法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多