如何在不初始化的情况下检查是否初始化了lazy val?

时间:2013-06-12 03:30:37

标签: scala

是否可以确定是否初始化了lazy val而没有初始化它?

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

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

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

6 个答案:

答案 0 :(得分:14)

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

你必须将变量称为optionalSubsystem()而不是optionalSubsystem,但这是 A Good Thing ,因为根据你想要的设计,获得该参考是一个明显的副作用程序。

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

答案 1 :(得分:3)

您可以这样做:

object TheApp {

    private var _optionalSubsystemInitialized = false

    def optionalSubsystemInitialized = _optionalSubsystemInitialized

    lazy val optionalSubsystem = {
        _optionalSubsystemInitialized = true
        subsystem
    }

}

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

答案 2 :(得分: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的监视器。有人谈论改变它。

另外,显然,仅当init值不是默认值(null.asInstanceOf[T])时才测试字段值。

第二种方法依赖于由惰性init加载的类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

答案 3 :(得分:2)

此解决方法如何?

val used = new AtomicBoolean(false)

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

if (used.get()) { /* initialized */ }

答案 4 :(得分:0)

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

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

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

这样“更多的依赖关系”会在最佳时间加载(包括从不需要的时候)

答案 5 :(得分: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)
  }
}