【问题标题】:Why don't property initializers call a custom setter?为什么属性初始化器不调用自定义设置器?
【发布时间】:2016-12-19 16:04:05
【问题描述】:

Kotlin documentation 开始,允许自定义设置器:

class Test {
  var stringRepresentation: String
    get() = field
    set(value) {
      setDataFromString(value) 
    }

  init {
    stringRepresentation = "test"
  }

  private fun setDataFromString(value: String) { }
}

但是你不能有一个没有自定义 getter 的自定义 setter(并从 init 块初始化):

class Test {
  // Compilation error: "Property must be initialized"
  var stringRepresentation: String
    set(value) {
      setDataFromString(value)
    }

  init {
    stringRepresentation = "test"
  }

  private fun setDataFromString(value: String) { }
}

虽然您可以在没有自定义 setter 的情况下使用自定义 getter,但这里没有问题:

class Test {
  var stringRepresentation: String
    get() = field 

  init {
    stringRepresentation = "test"
  }

  private fun setDataFromString(value: String) { }
}

那么为什么不能使用自定义设置器和从init 块中初始化的属性,为什么init 块在属性初始化器直接分配时调用自定义设置器,绕过自定义设置器?

class Test {
  var stringRepresentation: String = "" // Does not call custom setter
    set(value) {
      setDataFromString(value)
    }

  init {
    stringRepresentation = "test" // Calls custom setter
  }

  private fun setDataFromString(value: String) { }
}

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    属性初始化器不调用自定义设置器,因为它们的目的是提供默认值。

    与 Java 不同,在 Kotlin 中,不仅局部变量必须在首次访问之前初始化,而且类属性也必须初始化。

    在 Java 中这是有效的。

    public class Test {
        public String str;
    
        public static void main(String[] args) {
            System.out.println(new Test().str);
        }
    }
    

    在 Kotlin 中不是这样。

    class Parent {
        var str: String?
    }
    
    fun main(args: Array<String>) {
        Parent().str
    }
    

    因此,自定义 setter 需要由属性初始化程序或构造函数初始化其属性。看看下面的例子。

    class Test {
        var stringRepresentation: String = "a" // Default value. Does not call custom setter
            get() = field
            set(value) {
                println("Setting stringRepresentation property to %s. Current value is %s.".format(value, field))
                field = setDataFromString(value)
            }
    
        init {
            this.stringRepresentation = "b" // Calls custom setter
        }
    
        private fun setDataFromString(value: String): String {
            println("Setting stringRepresentation property to %s.".format(value))
            return value
        }
    }
    
    fun main(args: Array<String>) {
        Test().stringRepresentation = "c" // Calls custom setter
    }
    

    属性 stringRepresentation 在其类的实例化时初始化为 "a" opon 而无需调用 setter。 然后调用 init 块并使用 setter 将值设置为 "b"。 然后使用 setter 到 "c"

    【讨论】:

    • 只是有点难以理解为什么添加带有 init 块属性初始化的自定义设置器将无法编译(因为属性 is 正在初始化)。有没有办法避免初始化器和自定义设置器中的代码重复,而不委托给另一种方法?我想在初始化期间做一些工作,避免两次写入属性。每当调用set 时,都需要完成相同的工作。
    • 好吧,init 块确实初始化了属性。但是由于该属性有一个自定义设置器,因此初始化块使用此自定义设置器来执行此操作。由于自定义设置器需要使用默认值或构造函数初始化属性,因此您需要提供其中之一。
    • Kotlin 似乎是一门很好的语言,但在某些地方它离不开一勺屎......(((
    • @breandan,您可以通过将值初始化为某个随机值来避免重复,只是为了满足这个要求。然后在构造函数初始化程序块中,您可以通过设置来对属性值进行实际初始化,这将调用自定义设置器。例如,您可以在属性初始化程序中将 String 初始化为 "",然后在属性正下方有一个 init {} 块,该块使用传递给构造函数的值再次设置属性,调用设置器代码。尽管这可行,但它似乎是语言设计中的一个缺陷。
    • @still_dreaming_1 是的,感谢您的回复。这就是我试图避免的,重复初始化。但在这种情况下似乎是必要的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-29
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 2013-10-11
    • 2011-05-20
    • 2018-03-01
    相关资源
    最近更新 更多