可遍历的递归节点结构

时间:2012-08-20 12:10:56

标签: scala foreach stack-overflow tostring case-class

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

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。那么为什么我会出现堆栈溢出?

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

2 个答案:

答案 0 :(得分:5)

当您在toString上致电Atom时,您将获得Traversable的{​​{1}},the documentation描述如下:

  

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

通过调用集合foreach来实现“所有元素后跟”部分in TraversableOnce。由于foreach首次点击Node本身,您会立即遇到无限循环。

答案 1 :(得分:1)

通常,case类的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))
    }))
  };

上面,编译器“仅”生成hashCodeequalstoString继承自Traversable

Traversable包中的collection特征具有toString方法,该方法是根据foreach定义的。因此,调用toString会使节点本身foreach迭代(与f(this)一致),从而导致无限循环。