case class A(a: Int) {
lazy val k = {
println("k")
1
}
val a1 = A(5)
println(a1.k)
Output:
k
res1: Int = 1
println(a1.k)
Output:
res2: Int = 1
val a2 = A(5)
println(a1.k)
Output:
k
res3: Int = 1
我原以为对于println(a2.k(,它不应该打印k。既然这不是必需的行为,我应该如何实现这一,以便对于具有相同参数的case类的所有实例,它应该只执行一次惰性val定义我需要一些内存化技术吗?或者Scala可以自己处理?
我对Scala和函数式编程很陌生,所以如果你觉得这个问题很琐碎,请原谅我。
假设您没有重写equals
或做一些不明智的事情,例如使构造函数参数为var
s,那么在这种情况下,具有相同构造函数参数的两个case类实例化将相等。然而,这并不意味着具有相同构造函数参数的两个case类实例化将指向内存中的相同对象:
case class A(a: Int)
A(5) == A(5) // true, same as `A(5).equals(A(5))`
A(5) eq A(5) // false
如果您希望构造函数总是在内存中返回相同的对象,那么您需要自己处理。也许使用某种工厂:
case class A private (a: Int) {
lazy val k = {
println("k")
1
}
}
object A {
private[this] val cache = collection.mutable.Map[Int, A]()
def build(a: Int) = {
cache.getOrElseUpdate(a, A(a))
}
}
val x = A.build(5)
x.k // prints k
val y = A.build(5)
y.k // doesn't print anything
x == y // true
x eq y // true
相反,如果您不关心构造函数返回相同的对象,而只关心k
的重新评估,则可以缓存该部分:
case class A(a: Int) {
lazy val k = A.kCache.getOrElseUpdate(a, {
println("k")
1
})
}
object A {
private[A] val kCache = collection.mutable.Map[Int, Int]()
}
A(5).k // prints k
A(5).k // doesn't print anything
琐碎的答案是"这就是语言根据规范所做的"。这是正确的答案,但不是很令人满意。它为什么这么做更有趣。
更清楚的是,它有用另一个例子来做这件事:
case class A[B](b: B) {
lazy val k = {
println(b)
1
}
}
当你构造两个A
时,你不知道它们是否相等,因为你还没有定义它们相等意味着什么(或者B相等意味着哪里(。您也不能静态初始化k
,因为它取决于传入的B
。
如果必须打印两次,如果仅在k
依赖于b
的情况下是这样,而在不依赖于b
的情况下则不是这样,这将是完全直观的。
当你问
我应该如何实现这一点,以便对于具有相同参数的case类的所有实例,它应该只执行一次延迟val定义
这是一个比听起来更棘手的问题。您使"相同的参数"听起来像是在编译时可以知道的东西,而无需进一步的信息。不是,你只能在运行时知道。
如果您只在运行时知道这一点,那么这意味着您必须保持实例A[B]
过去的所有使用。这是一个内置的内存泄漏——难怪Scala没有内置的方法来做到这一点。
如果你真的想要这样做——并且仔细考虑内存泄漏——那么就构造一个Map[B, A[B]]
,并尝试从该映射中获取一个缓存实例,如果它不存在,那么构造一个并将其放入映射中。
我相信case class
只认为其构造函数(而不是任何辅助构造函数(的参数是其等式概念的一部分。考虑一下,当您在match
语句中使用case类时,unapply
只允许您访问(默认情况下(构造函数参数。
将case类主体中的任何内容都视为"额外"或"副作用"。我认为这是一个很好的策略,使case类尽可能接近空,并将任何自定义逻辑放在伴随对象中。例如:
case class Foo(a:Int)
object Foo {
def apply(s: String) = Foo(s.toInt)
}
除了dhg-answer,我应该说,我不知道函数式语言默认情况下会进行完整的构造函数记忆。你应该明白,这种记忆意味着所有构建的实例都应该保存在内存中,这并不总是可取的。
手动缓存没有那么难,考虑一下这个简单的代码
import scala.collection.mutable
class Doubler private(a: Int) {
lazy val double = {
println("calculated")
a * 2
}
}
object Doubler{
val cache = mutable.WeakHashMap.empty[Int, Doubler]
def apply(a: Int): Doubler = cache.getOrElseUpdate(a, new Doubler(a))
}
Doubler(1).double //calculated
Doubler(5).double //calculated
Doubler(1).double //most probably not calculated