【问题标题】:Instantiate a variable in Kotlin only if it is a null?仅当它为空时才在 Kotlin 中实例化一个变量?
【发布时间】:2019-07-01 06:29:24
【问题描述】:

比方说,我有一个变量:

var myObject : MyObject? = null

应该在某个地方清除它:

myObject?.clear
myObject = null

并且在使用的地方绝对不能为空。在 Java 中,我可以这样做:

private MyObject getMyObject(){
  if(myObject == null) {
    myObject = new MyObject()
  }
  return myObject
}

问题:如何在 Kotlin 中实现这一点?

我找到了使用 elvis-operator 的建议:

private fun getMyObject() = myObject ?: MyObject()

但这不会将结果(如果将创建 MyObject 的新实例)分配给 myObject 变量。 请帮我解决和解释。先谢谢了

【问题讨论】:

  • 你可以像在 Java 中一样:private fun getMyObject(): MyObject { if(myObject == null) { myObject = MyObject() } return myObject }
  • @Sergey - 当然,我知道我可以使用这种方法。问题是关于更优雅的解决方案

标签: java kotlin nullable getter backing-field


【解决方案1】:

您可以将类似 Java 的方法与 Elvis 运算符的用法结合起来,编写如下代码:

private fun getMyObject() : MyObject {
    myObject = myObject ?: MyObject()
    return myObject as MyObject
}

由于myObject 的声明:var myObject MyObject? = null,所以需要显式转换as MyObjectmyObject 是一个可为空的MyObject,但getObject 的返回类型是MyObject。 希望对你有帮助!

【讨论】:

    【解决方案2】:

    您可以使用属性的backing field

    class Foo {
    
        var bar: String? = null
            get() {
                if (field == null) {
                    field = "Automatically set"
                }
                return field
            }
    
    
    }
    

    尝试一下:

    fun main() {
        val foo = Foo()
        foo.bar = "Manually set"
        println(foo.bar)
        foo.bar = null
        println(foo.bar)
    }
    

    不幸的是,该属性必须可以为空才能正常工作。您必须在任何地方使用!!?.


    您也可以使用委托。这需要更多的代码来编写属性,但更容易在其他地方使用该属性。

    class Foo(initBar: String? = null) {
    
        private val barDelegate = NullDelegate(initBar) { "Automatically set" }
        var bar: String by barDelegate // not nullable to outside world
    
        fun clearBar() {
            barDelegate.clear()
        }
    
    }
    
    // Reusable. Not thread-safe.
    class NullDelegate<T>(
        private var value: T? = null, 
        private val supplier: () -> T
    ) {
    
        operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
            if (value == null) value = supplier()
            return value!!
        }
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
            this.value = value
        }
    
        fun clear() {
            value = null
        }
    
    }
    

    要将bar 设置为null,您需要调用foo.clearBar() 而不是foo.bar = null

    【讨论】:

    • 使用代表的好方法!
    【解决方案3】:

    答案:

    @Slaw 版本的较短版本如下:

    var myObject: MyObject? = null
        get() {
            field = field ?: MyObject()
            return field
        }
    

    如果在 get 访问中属性是 null,这将实例化一个新的 MyObject 到支持字段。


    其他信息:

    您可能会想到类似于以下的较短版本

    var myObject: Any? = null
        get() = field = field ?: Any() // Does not compile
    

    请记住,这不会编译错误消息:Assignments are not expressions, and only expressions are allowed in this context

    【讨论】:

      【解决方案4】:

      问题是属性的 getter 和 setter 不能有不同的类型。我建议一个单独的可为空的私有属性和清除它的方法:

      private var _myObject: MyObject? = null
      
      var myObject: MyObject // or val, depending
          get() {
              if (_myObject == null) { _myObject = MyObject() }
              return _myObject!!
          }
          set(value: MyObject) { 
              _myObject?.clear()
              _myObject = value
          }
      
      fun clearMyObject() {
          _myObject?.clear()
          _myObject = null
      }
      

      如果您多次需要此模式,请写delegate

      【讨论】:

        【解决方案5】:

        您几乎已经通过 elvis 运算符找到了正确答案,唯一缺少的部分是将新创建的实例分配回 myObject 变量:

        private fun getMyObject() = myObject ?: MyObject().also { myObject = it }
        

        【讨论】:

          猜你喜欢
          • 2018-02-17
          • 1970-01-01
          • 2023-03-08
          • 2021-01-23
          • 2015-06-04
          • 1970-01-01
          • 2014-06-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多