1

In the book "Scala for the impatient" by Cay Horstmann, on chapter 5 exercise 8, there is this question:

//Make a class Car with read-only properties for manufacturer, model name,
//and model year, and a read-write property for the license plate. Supply four
//  constructors. All require the manufacturer and model name. Optionally,
//model year and license plate can also be specified in the constructor. If not,
//the model year is set to -1 and the license plate to the empty string. Which
//constructor are you choosing as the primary constructor? Why?

So I coded the class:

case class Car(val manufacturer: String, val modelName: String,
                 val modelYear: Int, var licensePlate : String = "") {
    def this(manufacturer: String, modelName: String, licensePlate: String) {
      this(manufacturer, modelName, -1, licensePlate)
    }

    def this(manufacturer: String, modelName: String, modelYear: Int) {

specifically on this part:

      this(manufacturer, modelName, modelYear)
    }

    def this(manufacturer: String, modelName: String) {
      this(manufacturer, modelName, -1)
    }
  }

The compiler complains of the error:

<console>:14: error: called constructor's definition must precede calling constructor's definition
             this(manufacturer, modelName, modelYear)
             ^

Why does it complain when I have provided a default value for the license Plate as an empty String in the primary constructor, and it sure is defined first??

The error goes away if I do this:

this(manufacturer, modelName, modelYear, "")

or if I make the class primary constructor not have a default value for the licensePlate (of course adjusting other auxilliary constructor calls in the process).

Note that the question specifically asked for 4 constructors, so instance creation can be called like:

new Car("Honda", "City", 2010, "ABC-123")
new Car("Honda", "City")
new Car("Honda", "City", "ABC-123")
new Car("Honda", "City", 2010)

Thanks in advance to those who can shed light on this issue.

====

Thanks to answers by Ende Neu and Kigyo, this looks like the perfect solution (remove the recursive constructor):

case class Car(val manufacturer: String, val modelName: String,
               val modelYear: Int = -1, var licensePlate : String = "") {
  def this(manufacturer: String, modelName: String, licensePlate: String) {
    this(manufacturer, modelName, -1, licensePlate)
  }

  << removed constructors here >>
}

Console println new Car("Honda", "City", 2010, "ABC-123")
Console println new Car("Honda", "City")
Console println new Car("Honda", "City", "ABC-123")
Console println new Car("Honda", "City", 2010)
ailveen
  • 583
  • 3
  • 14
  • If you are on that road now, you can also have a default value for `modelYear`. Then you could also remove the last constructor. I thought your idea was to have four constructors. :) – Kigyo Jun 30 '14 at 00:14
  • Perfectionists are we, Kigyo? I have updated it again (see above). Now this is not that perfect. I am still passing a -1 to the model year in the auxilliary constructor even though I have a default in the primary constructor. Can this be improved further? – ailveen Jul 01 '14 at 13:03
  • I do not find an improvement in this case. :( – Kigyo Jul 01 '14 at 14:56

2 Answers2

2

I just want to make some things clear.

You can leave out the last parameter in this example. Here we call the primary constructor, that has 3 parameters, with only supplying two. (related to Ende Neu's example)

case class Test(a: String, b: String, c: String = "test") {
  def this() = this("", "")
}

So why does it not work in your scenario? Have a close look!

def this(manufacturer: String, modelName: String, modelYear: Int) {
  this(manufacturer, modelName, modelYear)
}

Here you also leave out the last parameter, but the new constructor you define takes exactly that amount of parameters and this makes it a recursive call. So you never call the primary constructor and therefore you get the error.

I would also remove the default value in your case. Maybe there is a way around it, but I'm too tired at the moment.

Kigyo
  • 5,668
  • 1
  • 20
  • 24
  • I did not intend to make a recursive call here, but it sure looks so. I was blinded by the fact that by doing this I am calling the primary constructor that takes 4 parameters. I guess you are right, the scala compiler should react with a compile error. The perfect solution I guess is to remove this constructor. – ailveen Jun 29 '14 at 23:52
  • I missed that, probably was too late for me, indeed this is the right answer. – Ende Neu Jun 30 '14 at 06:37
1

You get this error because the scala compiler is not able to see the default value you provided in your case class and you are declaring a constructor with three parameters instead of four, for example:

case class Test(a: String, b: String, c: String = "test") {
  def this(d: String, e: String) = this(c, e)
}

This will throw the same error, what you can do is to specify default values in the constructor as you did with the empty string:

def this(d: String) = this(d, "empty")
def this(d: String, e: String) = this(d, e, "empty") // and this would return a new object

Having a case class with default value simply corresponds to a constructor where you don't have to serve all parameters, so for the exercise you don't need two constructors, if the year and the plate are not specified use some predifined value, else use the default constructor (which takes already 4 parameters):

case class Car(val manufacturer: String, val modelName: String, val modelYear: Int, var licensePlate : String) {
  def this(manufacturer: String, modelName: String) =
    this(manufacturer, modelName, -1, "")
  }
}

And then call it like this:

new Car("Wolksvagen", "Polo")
Car("Ford", "Fiesta", 1984, "plate")

Note that you have to use the new keyword in the first declaration because it's not referring to the apply method in the companion object (which could also be overridden, see this SO question).

Community
  • 1
  • 1
Ende Neu
  • 15,581
  • 5
  • 57
  • 68
  • You have a typo in your `Test` class. It should be `def this(d: String, e: String) = this(d, e)` – Kigyo Jun 29 '14 at 23:34
  • Good to know, it was not emphasized enough in the book. However, the question specifically asked for 4 constructors, so I guess I'll go with the solution to remove the default value in the primary constructor, so I can call instance creation like this (all 4 variants): new Car("Honda", "City", 2010, "ABC-123") new Car("Honda", "City") new Car("Honda", "City", "ABC-123") new Car("Honda", "City", 2010) – ailveen Jun 29 '14 at 23:37