我正在尝试在节点结构上实现递归遍历:
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
类和从Node
到TraversableNode
的隐式转换解决了这个特殊问题,但我仍然想知道导致堆栈溢出的原因。感谢。
答案 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))
}))
};
上面,编译器“仅”生成hashCode
和equals
,toString
继承自Traversable
。
Traversable
包中的collection
特征具有toString
方法,该方法是根据foreach
定义的。因此,调用toString
会使节点本身foreach
迭代(与f(this)
一致),从而导致无限循环。