在 Scala 中,将部分构造的对象留在周围通常不是理想的做法。你通常会重新考虑你的对象是如何被实例化的,看看你是否不能使用不那么脆弱的不同模式。例如,不要在方法中设置未初始化的变量:
class Foo { var a: String = null; var b: String = null }
def initFooA(s: String, f: Foo) { if (f.a == null) f.a = s }
def initFooB(s: String, f: Foo) { if (f.b == null) f.b = s }
f
initFooA("salmon", f)
// Do stuff
initFooB("herring", f)
您将尝试重组代码以按需生成所需的值,并将 foo 的实例化延迟到那时:
case class Bar(a: String, b: String) {}
def initBarA(s: String) = s
def initBarB(s: String) = s
val iba = initBarA("halibut")
// Do stuff
val ibb = initBarB("cod")
Bar(iba, ibb)
因为 Scala 可以轻松访问元组(和类型推断),所以这比在 Java 中要少得多。
您可以做的另一件事是将后期初始化推迟到其他人。
case class Baz(a: String)(bMaker: => String) {
lazy val b = bMaker
}
现在你传入一些将成为参数 b 的东西,并安排它处理任何需要处理的后期初始化内容。这并不总是避免需要设置变量,但它可以帮助将它从你的类代码中推出到你的初始化逻辑中(这通常是一个更好的地方)。
用 vars 做这件事有点不那么简单。实际上,您最好只为它上一堂课,例如:
class LazyVar[A](initial: => A) {
private[this] var loaded = false
private[this] var variable: A = _
def apply() = { if (!loaded) { loaded = true; variable = initial }; variable }
def update(a: A) { loaded = true; variable = a }
}
然后(可悲地)您必须()
在每次读写时使用。
scala> val lv = new LazyVar({ println("Hi!"); 5 })
lv: LazyVar[Int] = LazyVar@2626ea08
scala> lv()
Hi!
res2: Int = 5
scala> lv() = 7
scala> lv()
res4: Int = 7
然后你使用这个类的一个实例而不是实际的var
并通过惰性初始化器。(lazy val
在引擎盖下非常像这样;编译器只是保护你不被注意到。)
最后,如果您想拥有一个偶尔缺少值的全功能对象,var x: Option[X]
那么您要使用的构造是;如果您无法找到绕过标准 Java 创建模式的方法(并且您不想尝试一些更奇特的东西,例如使用越来越多的信息相互创建的对象,或者因为性能至关重要而您负担不起,或者你不喜欢写那么多样板来允许类型检查来验证你的对象是否被正确创建)但是你想使用它,这var x: X = null
是我会选择的,而不是_
. IfX
是一个原语,您可能无论如何都需要明智地选择正确的值(例如,Double.NaN
而不是0.0
,-1
而不是0
forInt
) 表示我未初始化。如果它是通用代码,并且您想要Any
而不是AnyRef
, asInstanceOf
-ing 在Any
并且AnyRef
可能是摆脱类型检查不佳情况的最佳方法(假设您真的,真的不能使用Option
,此时更清楚)。