20

是否可以在不初始化的情况下确定惰性 val 是否已初始化?

object TheApp {
    lazy val optionalSubsystem = {
        // ...
        subsystem
    }

    def main(args: Array[String]) {
        bootSubsystemA(this)
        bootSubsystemB(this)

        if (/* optionalSubsystem is initialized */) {
            // more dependencies
        }
    }
}
4

6 回答 6

17

这并不是您问题的真正答案,我讨厌人们这样做,但无论如何我都会这样做。我认为最好的回应是:懒惰的 val 不适合这个,所以定义一个支持你需要的类型。

您必须将变量称为optionalSubsystem()而不是optionalSubsystem,但这是一件好事,因为对于您想要的设计,获得该引用是一个明显的副作用过程。

class Lazy[A](f: => A, private var option: Option[A] = None) {

  def apply(): A = option match {
    case Some(a) => a
    case None => val a = f; option = Some(a); a
  }

  def toOption: Option[A] = option

}

scala> val optionalSubsystem = new Lazy { "a" }
optionalSubsystem: Lazy[java.lang.String] = Lazy@1210267

scala> optionalSubsystem.toOption.isDefined
res1: Boolean = false

scala> optionalSubsystem()
res2: java.lang.String = a

scala> optionalSubsystem.toOption.isDefined
res12: Boolean = true

编辑- 感谢 Tomas Mikula,这是另一个经过一些修改的修订:

import scala.language.implicitConversions

object Lazy {

  def lazily[A](f: => A): Lazy[A] = new Lazy(f)

  implicit def evalLazy[A](l: Lazy[A]): A = l()

}

class Lazy[A] private(f: => A) {

  private var option: Option[A] = None

  def apply(): A = option match {
    case Some(a) => a
    case None => val a = f; option = Some(a); a
  }

  def isEvaluated: Boolean = option.isDefined

}

这使您可以编写lazily { ... }而不是new Lazy { ... },而optionalSubsystem不是optionalSubsystem()

scala> import Lazy._
import Lazy._

scala> val optionalSubsystem = lazily { "a" }
optionalSubsystem: Lazy[String] = Lazy@3d0d54

scala> optionalSubsystem.isEvaluated
res0: Boolean = false

scala> optionalSubsystem: String
res1: String = a

scala> optionalSubsystem.isEvaluated
res2: Boolean = true
于 2013-06-12T04:05:22.023 回答
3

你可以这样做:

object TheApp {

    private var _optionalSubsystemInitialized = false

    def optionalSubsystemInitialized = _optionalSubsystemInitialized

    lazy val optionalSubsystem = {
        _optionalSubsystemInitialized = true
        subsystem
    }

}

在 a 的初始化代码中出现这样的副作用是否真的合适lazy val是另一个问题。

于 2013-06-12T04:13:37.517 回答
2

但当然可以。一个字段只是一个字段。

package lazyside

object Lazy

class Foo {
  lazy val foo = 7
  lazy val bar = { Lazy ; 8 }
}

object Test extends App {
  import scala.reflect.runtime.{ currentMirror => cm }
  import scala.reflect.runtime.universe._

  val x = new Foo

  // method 1: reflect the underlying field
  val im = cm reflect x
  val f  = (typeOf[Foo] declaration TermName("foo")).asTerm.accessed.asTerm
  def foo_? = x synchronized ((im reflectField f).get != 0)

  def yn(b: Boolean) = if (b) "yes" else "no"
  Console println s"Is foo set yet? ${yn(foo_?)}"

  // method 2: check a benign side effect like a class load
  val m = classOf[ClassLoader].getDeclaredMethod("findLoadedClass", classOf[String])
  m setAccessible true
  def bar_? = (m invoke (x.getClass.getClassLoader, "lazyside.Lazy$")) != null
  Console println s"Is bar set yet? ${yn(bar_?)}"

  Console println s"I see that foo is ${x.foo}."
  Console println s"Is foo set yet? ${yn(foo_?)}"
  Console println s"I see that bar is ${x.bar}."
  Console println s"Is bar set yet? ${yn(bar_?)}"
  Console println s"I see that x is loaded by a ${x.getClass.getClassLoader.getClass}"
}

需要注意的是,线程安全性foo_?依赖于获取实例监视器的惰性计算x。有人说要改变这一点。

此外,显然,只有在初始值不是默认值 ( null.asInstanceOf[T]) 时测试字段值才有效。

第二种方法依赖于Lazy$惰性初始化加载的类。将对象放入 Foo 中会更安全一些。无论如何,这种特殊的副作用是一次性的。这可能满足子系统启动的用例。

输出不出所料:

Is foo set yet? no
Is bar set yet? no
I see that foo is 7.
Is foo set yet? yes
I see that bar is 8.
Is bar set yet? yes
I see that x is loaded by a class scala.reflect.internal.util.ScalaClassLoader$URLClassLoader

在 2.11 中编译。对于 2.10,使用newTermName而不是TermName.

于 2013-06-12T06:35:36.227 回答
2

这个解决方法怎么样?

val used = new AtomicBoolean(false)

lazy val o: String = {
  used.set(true)
  "aaa"
}

if (used.get()) { /* initialized */ }
于 2019-01-22T15:42:40.433 回答
0

不是直接的,但你为什么不像这样改变你的逻辑:

object TheApp {
    lazy val optionalSubsystem = {
        // ...
        subsystem
        // more dependencies
    }

    def main(args: Array[String]) {
        bootSubsystemA(this)
        bootSubsystemB(this)
    }
}

这样,“更多依赖项”会在最佳时间加载(如果不需要,则永远不会加载)

于 2013-06-12T13:08:14.880 回答
0

为了保持不变,您必须处理状态更改。

您可以使用以下 Lazy 来做您想做的事情:

trait Lazy[T] {
  def act[U](f: T => U): (Lazy[T], U)
  def actIfInitialized[U](f: T => U): (Lazy[T], Option[U])
  def isInitialized: Boolean
}

case class UnInitializedLazy[T](builder: () => T) extends Lazy[T] {
  override def isInitialized: Boolean = false

  override def act[U](f: T => U): (Lazy[T], U) = {
    InitializedLazy(builder()).act(f)
  }

  override def actIfInitialized[U](f: T => U): (Lazy[T], Option[U]) = {
    (this, None)
  }
}

case class InitializedLazy[T](thing: T) extends Lazy[T] {
  override def isInitialized: Boolean = false

  override def act[U](f: T => U): (Lazy[T], U) = {
    (this, f(thing))
  }

  override def actIfInitialized[U](f: T => U): (Lazy[T], Option[U]) = {
    (this, Some(f(thing)))
  }
}

你会这样使用它:

class example extends FlatSpec with Matchers {

  it should "initialize and act" in {
    val lazyBob: Lazy[String] = UnInitializedLazy(() => "Bob")
    val (bob, upperBob) = personToUpper(lazyBob)

    upperBob shouldBe "BOB"

    bob.isInitialized shouldBe true
  }

  it should "act since it was initialized" in {
    val lazyBob: Lazy[String] = UnInitializedLazy(() => "Bob")
    val (bob, upperBob) = personToUpper(lazyBob)

    var res: Boolean = false

    upperBob shouldBe "BOB"

    bob.isInitialized shouldBe true
    bob.actIfInitialized(_ => res = true)

    res shouldBe true
  }

  it should "not act since it was not initialized" in {
    val lazyBob: Lazy[String] = UnInitializedLazy(() => "Bob")

    var res: Boolean = false

    lazyBob.isInitialized shouldBe false
    lazyBob.actIfInitialized(_ => res = true)
    lazyBob.isInitialized shouldBe false

    res shouldBe false
  }

  def personToUpper(person: Lazy[String]): (Lazy[String], String) = {
    // Here you will initialize it and do the computing you want.
    // The interest is that you will not need to know how to instanciate Bob
    // since it was defined before, you just do your computations and return a state.
    person.act(p => p.toUpperCase)
  }
}
于 2019-06-18T08:06:43.707 回答