Nicola Ferraro
Nicola Ferraro

Reputation: 4189

Why does overloaded method using varargs cause StackOverflowError?

I implement a Scala class with an overloaded method that can take an Iterable[String] or a String* varargs parameter:

class StackOverflow(names: Iterable[String]) {

  // This function creates a copy of the StackOverflow object
  // copy is needed but this cannot be a case class.
  private def copy(names: Iterable[String] = names) = new StackOverflow(names) // <- line 19

  // overloaded methods

  def withNames(names: Iterable[String]) = this.copy(names = names) // <- line 24

  def withNames(names: String*) = require(names.nonEmpty); withNames(names.toIterable) // <- line 26
}

object App {

  def main(args: Array[String]) = {
    val x1 = new StackOverflow(Seq("a", "b"))
    val x2 = x1.withNames("c", "d")
  }
}

I expect x2 to be a new object with names c and d, but the value x2 cannot be created because of an infinite recursion causing a StackOverflowError:

Exception in thread "main" java.lang.StackOverflowError
    at scala.collection.LinearSeqLike$class.thisCollection(LinearSeqLike.scala:48)
    at scala.collection.immutable.List.thisCollection(List.scala:84)
    at scala.collection.immutable.List.thisCollection(List.scala:84)
    at scala.collection.IterableLike$class.toIterable(IterableLike.scala:87)
    at scala.collection.AbstractIterable.toIterable(Iterable.scala:54)
    at test.StackOverflow.<init>(StackOverflow.scala:26)
    at test.StackOverflow.copy(StackOverflow.scala:19)
    at test.StackOverflow.withNames(StackOverflow.scala:24)
    at test.StackOverflow.<init>(StackOverflow.scala:26)
    at test.StackOverflow.copy(StackOverflow.scala:19)
    at test.StackOverflow.withNames(StackOverflow.scala:24)
    ...

What's wrong with the code?

Upvotes: 1

Views: 346

Answers (1)

David Frank
David Frank

Reputation: 6092

You have been trapped by the omission of braces.

You don't even need the val x2 = x1.withNames("c", "d") line.

def withNames(names: String*) = require(names.nonEmpty); withNames(names.toIterable)

This is actually:

def withNames(names: String*) = require(names.nonEmpty) // part of class
withNames(names.toIterable)  // part of constructor

withNames(names.toIterable) is absolutely correct, since names is also a field in your class.

As a result, whenever you instantiate a StackOverflow object, the constructor calls withNames() that creates a new instance, that calls withNames() and so on. To fix this, you must use braces around the method definition. And of course, since you are overloading withNames() you must specify the return type as well.

Upvotes: 6

Related Questions