3

我正在尝试在节点结构上实现递归遍历:

sealed class Node(subnodes: Traversable[Node]) extends Traversable[Node] {
  def foreach[U](f: Node => U) {
    f(this)
    subnodes foreach f
  }
}

case class Atom(id: String) extends Node(Nil)

case class Molecule(atoms: List[Node]) extends Node(atoms)

调用toString类似的元素Atom("test").toString会导致堆栈溢出:

Exception in thread "main" java.lang.StackOverflowError
at java.lang.System.arraycopy(Native Method)
at java.lang.String.getChars(Unknown Source)
at java.lang.AbstractStringBuilder.append(Unknown Source)
at java.lang.StringBuilder.append(Unknown Source)
at scala.collection.mutable.StringBuilder.append(StringBuilder.scala:197)
at scala.collection.TraversableOnce$class.addString(TraversableOnce.scala:297)
at Node.addString(Fail.scala:1)
at scala.collection.TraversableOnce$class.mkString(TraversableOnce.scala:263)
at Node.mkString(Fail.scala:1)
at scala.collection.TraversableLike$class.toString(TraversableLike.scala:615)
at Node.toString(Fail.scala:1)
at java.lang.String.valueOf(Unknown Source)
at scala.collection.mutable.StringBuilder.append(StringBuilder.scala:187)
at scala.collection.TraversableOnce$$anonfun$addString$1.apply(TraversableOnce.scala:300)
[...]
at Node.foreach(Fail.scala:3)
at scala.collection.TraversableOnce$class.addString(TraversableOnce.scala:298)
at Node.addString(Fail.scala:1)
at scala.collection.TraversableOnce$class.mkString(TraversableOnce.scala:263)
at Node.mkString(Fail.scala:1)
at scala.collection.TraversableLike$class.toString(TraversableLike.scala:615)
at Node.toString(Fail.scala:1)
at java.lang.String.valueOf(Unknown Source)
at scala.collection.mutable.StringBuilder.append(StringBuilder.scala:187)
at scala.collection.TraversableOnce$$anonfun$addString$1.apply(TraversableOnce.scala:300)

请注意,我没有在任何地方明确调用 foreach。那么为什么会出现堆栈溢出呢?

我通过一个额外的类和从toTraversableNode的隐式转换解决了这个特殊问题,但我仍然想知道是什么导致了堆栈溢出。谢谢。NodeTraversableNode

4

2 回答 2

5

当你调用toStringanAtom时,你会得到一个 for Traversable文档描述如下:

默认情况下,此字符串由stringPrefix该集合的 组成,后跟以逗号分隔并用括号括起来的所有元素。

“跟随所有元素”部分是通过TraversableOnce调用集合的foreach. 由于您的foreach第一次命中Node本身,您立即陷入无限循环。

于 2012-08-20T12:23:44.997 回答
1

通常,toString案例类的方法会打印类名及其参数列表(第一个参数列表中的那些)。如果您的案例类扩展了具有明确定义的类,则此规则有一个例外toString

你的Node扩展Traversable[Node]。请注意,它没有编译器生成的 case-class toString,如其 AST ( scalac -Xprint:cleanup) 所示:

case class Atom extends Node with Product with Serializable {
   ....
    override <synthetic> def hashCode(): Int = ScalaRunTime.this._hashCode(Atom.this);
    override <synthetic> def equals(x$1: Any): Boolean = Atom.this.eq(x$1.asInstanceOf[Object]()).||(x$1.isInstanceOf[Atom]().&&({
      <synthetic> val Atom$1: Atom = x$1.asInstanceOf[Atom]();
      Atom.this.id().==(Atom$1.id()).&&(Atom$1.canEqual(Atom.this))
    }))
  };

上面,编译器“仅”生成hashCodeand equalstoString继承自Traversable.

包中的Traversabletrait有一个根据. 因此,调用会在节点本身上进行迭代(与 一致),从而导致无限循环。collectiontoStringforeachtoStringforeachf(this)

于 2012-08-20T12:17:24.090 回答