Scala,为什么 lazy val 在枚举中表现得如此神秘?

Why does lazy val behave so mysteriously in enumeration, Scala?

我想懒惰地找到所有定义的枚举值名称中的最大值。下面这段代码执行时陷入死循环,重复打印InvocationTargetException.

object Enum extends Enumeration with App {
  val A, B = new Val
  lazy val foo = values.maxBy(_.toString)

  println(Enum.foo)
}

foodef时不会出现该问题。

为什么会这样? lazy val不应该只是一个背诵的def吗?

奇怪的是,以下代码确实按预期工作:

object Enum extends Enumeration with App {
  val A, B = new Val
  lazy val foo = values.foldLeft(""){(a, b) => a + b}

  println(Enum.foo)
}

我正在使用 scala 2.11.7。

// --- 解决方案

问题是 foo 是 Value 类型,因此被解释为枚举值。 解决该问题的方法是创建一个 deflazy val 的代理,如以下代码片段所示:

object Enum extends Enumeration with App {
  val A, B = new Val
  private lazy val fooBuffer = values.maxBy(_.toString)
  def foo = fooBuffer

  println(Enum.foo)
}

两个问题是App必须由Enum main null初始化。

然后,Enumeration 查找 Val 类型的 vals,其中包括您的成员 foo,这可能不是预期的。

示例混乱:

scala> :pa
// Entering paste mode (ctrl-D to finish)

object Enum extends Enumeration with App {
  val A, B = new Val
  lazy val foo = values.maxBy(_.toString)

  println(Enum.foo)
}

// Exiting paste mode, now interpreting.

defined object Enum

scala> Enum main null
foo

scala> Enum.foo
res2: Enum.Value = foo

scala> Enum.values
res3: Enum.ValueSet = Enum.ValueSet(A, foo)

只是为了表明修复类型修复了 SO:

scala> object X extends Enumeration { val x, y, z = new Val ; lazy val f: Any = values.maxBy(_.toString) }
defined object X

scala> X.f
res2: Any = z

我不知道你是如何初始化你的对象的,但我看到的是 WhosebugError(无论你是否将初始化延迟到 App):

java.lang.WhosebugError
  at java.lang.reflect.Method.getParameterTypes(Method.java:264)
  at scala.Enumeration$$anonfun.apply(Enumeration.scala:161)
  at scala.Enumeration$$anonfun.apply(Enumeration.scala:161)
  at scala.collection.TraversableLike$$anonfun$filterImpl.apply(TraversableLike.scala:259)
  at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
  at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
  at scala.collection.TraversableLike$class.filterImpl(TraversableLike.scala:258)
  at scala.collection.TraversableLike$class.filter(TraversableLike.scala:270)
  at scala.collection.mutable.ArrayOps$ofRef.filter(ArrayOps.scala:186)
  at scala.Enumeration.scala$Enumeration$$populateNameMap(Enumeration.scala:161)
  at scala.Enumeration$$anonfun$scala$Enumeration$$nameOf.apply(Enumeration.scala:180)
  at scala.Enumeration$$anonfun$scala$Enumeration$$nameOf.apply(Enumeration.scala:180)
  at scala.collection.MapLike$class.getOrElse(MapLike.scala:128)
  at scala.collection.AbstractMap.getOrElse(Map.scala:59)
  at scala.Enumeration.scala$Enumeration$$nameOf(Enumeration.scala:180)
  at scala.Enumeration$Val.toString(Enumeration.scala:223)
  at Enum$$anonfun$foo.apply(<console>:55)
  at Enum$$anonfun$foo.apply(<console>:55)
  at scala.collection.TraversableOnce$$anonfun$maxBy.apply(TraversableOnce.scala:241)
  at scala.collection.TraversableOnce$$anonfun$maxBy.apply(TraversableOnce.scala:240)
  at scala.collection.Iterator$class.foreach(Iterator.scala:742)
  at scala.collection.AbstractIterator.foreach(Iterator.scala:1194)
  at scala.collection.IterableLike$class.foreach(IterableLike.scala:72)
  at scala.collection.AbstractIterable.foreach(Iterable.scala:54)
  at scala.collection.TraversableOnce$class.maxBy(TraversableOnce.scala:240)
  at scala.collection.AbstractTraversable.maxBy(Traversable.scala:104)
  at Enum$.foo$lzycompute(<console>:55)
  at Enum$.foo(<console>:55)
  at sun.reflect.GeneratedMethodAccessor66.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)
  at scala.Enumeration$$anonfun$scala$Enumeration$$populateNameMap.apply(Enumeration.scala:168)
  at scala.Enumeration$$anonfun$scala$Enumeration$$populateNameMap.apply(Enumeration.scala:165)
...

原因很可怕 source code 是在做一些肮脏的反射启发式算法。基本上 populateNameMap 在枚举的主体中查找类型 Value 的值。由于 lazy val foo 的类型 Value 包含在 populateNameMap 的主体中。所以你在这里有一个无限循环。

当您从 lazy val 更改为 def 时,populateNameMap 不再包含 foo,因为它不是 ValDef