Scala:创建对象时循环引用?

9

我无意中遇到了这样的情况(为了隔离问题,以下示例进行了简化):

abstract class Element(val other: Element)

case object First extends Element(Second)
case object Second extends Element(First)

object Main {
  def main(arguments: Array[String]) {
    val e1 = First
    val e2 = Second
    println("e1: "+e1+"   e1.other: "+e1.other)
    println("e2: "+e2+"   e2.other: "+e2.other)
  }
}

有人想猜测输出结果吗?:-)
e1: First   e1.other: Second
e2: Second   e2.other: null

输出结果有点意义。显然,当第二个对象被创建时,第一个对象还不存在,因此被赋值为null。问题是...这太错误了!我花了几个小时来追踪这个问题。编译器难道不应该告诉我们一些信息吗? 有趣的是,当我尝试将代码作为Scala脚本运行(相同的代码,去掉object Maindef main行以及关闭的}),我得到了一个无限序列(实际上不是无限的——在某个点上列表停止了,我猜测是由于异常跟踪深度的某些限制或其他原因)像这样的异常:
vilius@blackone:~$ scala 1.scala
...
at Main$$anon$1.Main$$anon$$Second(1.scala:4)
at Main$$anon$1$First$.<init>(1.scala:3)
at Main$$anon$1.Main$$anon$$First(1.scala:3)
at Main$$anon$1$Second$.<init>(1.scala:4)
at Main$$anon$1.Main$$anon$$Second(1.scala:4)
at Main$$anon$1$First$.<init>(1.scala:3)
...

我希望在运行时至少获得一些信息...

好的。我已经发泄完了。现在我想问一些问题。 :) 那么,你能推荐任何指向另一个案例对象的漂亮设计吗?顺便说一下,在我的实际情况中,有几个对象以循环方式指向下一个和前一个实例(最后一个指向第一个,反之亦然)。

使用Scala 2.8.1-final

编辑: 我找到了解决我的主要问题的方法:

abstract class Element {
  val other: Element
}
case object First extends Element {
  val other = Second
}
case object Second extends Element {
  val other = First
}

这似乎在编译版本中有效(但不适用于Scala脚本!)。有人可以解释一下发生了什么吗?

编辑2: 这个脚本有效(相同的内容,只是使用了def):

abstract class Element { def other: Element }
case object First extends Element { def other = Second }
case object Second extends Element { def other = First }

脚本的视角较少。尝试将所有内容放入其他对象中,以便清楚地表明需要一起解释所有内容。 - Rex Kerr
1个回答

10

通常的方式如下(更改了嵌套方式,以便您可以将其粘贴到REPL中):

object Main{
  abstract class Element(other0: => Element) {
    lazy val other = other0
  }

  case object First extends Element(Second)
  case object Second extends Element(First)

  def main(arguments: Array[String]) {
    val e1 = First
    val e2 = Second
    println("e1: "+e1+"   e1.other: "+e1.other)
    println("e2: "+e2+"   e2.other: "+e2.other)
  }
}

也就是说,将按名称传递的参数放入懒惰值中以供将来使用。


编辑:您发现的修复方法有效是因为对象本身是懒惰的,您可以引用它们,但在使用它们之前不会创建它们。因此,一个对象可以自由地将自己指向另一个对象,而无需要求另一个对象已经初始化。


你的解决方案比我的EDIT2更好吗? - Vilius Normantas
“better” 是什么意思?如果你要处理多个对象,我的版本需要较少的输入,但如果你只需要两个项目,你的版本会略微(尽管可能无关紧要)更快,并且更加紧凑。 - Rex Kerr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接