How to declare constructor parameters when extending a Scala class

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 4.10, “How to handle constructor parameters when extending a Scala class.”

Problem

You want to extend a base Scala class, and need to work with the constructor parameters declared in the base class, as well as new parameters in the subclass.

Solution

Declare your base class as usual with val or var constructor parameters. When defining a subclass constructor, leave the val or var declaration off of the fields that are common to both classes. Then define new constructor parameters in the subclass as val or var fields, as usual.

For example, first define a Person base class:

class Person (var name: String, var address: Address) {
    override def toString = if (address == null) name else s"$name @ $address"
}

Next define Employee as a subclass of Person, so that it takes the constructor parameters name, address, and age. The name and address parameters are common to the parent Person class, so leave the var declaration off of those fields, but age is new, so declare it as a var:

class Employee (name: String, address: Address, var age: Int)
extends Person (name, address) {
    // rest of the class
}

With this Employee class and an Address case class:

case class Address (city: String, state: String)

you can create a new Employee as follows:

val teresa = new Employee("Teresa", Address("Louisville", "KY"), 25)

By placing all that code in the REPL, you can see that all of the fields work as expected:

scala> teresa.name
res0: String = Teresa

scala> teresa.address
res1: Address = Address(Louisville,KY)

scala> teresa.age
res2: Int = 25

Discussion

To understand how constructor parameters in a subclass work, it helps to understand how the Scala compiler translates your code. Because the following Person class defines its constructor parameters as var fields:

class Person (var name: String, var address: Address) {
    override def toString = if (address == null) name else s"$name @ $address"
}

the Scala compiler generates both accessor and mutator methods for the class. You can demonstrate this by compiling and then disassembling the Person class.

First, put this code in a file named Person.scala:

case class Address (city: String, state: String)

class Person (var name: String, var address: Address) {
  override def toString = if (address == null) name else s"$name @ $address"
}

Then compile the code with scalac, and disassemble the Person.class file with javap:

$ javap Person

Compiled from "Person.scala"
public class Person extends java.lang.Object implements scala.ScalaObject{
    public java.lang.String name();
    public void name_$eq(java.lang.String);
    public Address address();
    public void address_$eq(Address);
    public java.lang.String toString();
    public Person(java.lang.String, Address);
}

As shown, the Person class contains the name, name_$eq, address, and address_$eq methods, which are the accessor and mutator methods for the name and address fields. (See Recipe 6.8 for an explanation of how those mutator methods work.)

This raises the question, if you define an Employee class that extends Person, how should you handle the name and address fields in the Employee constructor? Assuming Employee adds no new parameters, there are at least two main choices:

// Option 1: define name and address as 'var'
class Employee (var name: String, var address: Address)
extends Person (name, address) { ... }

// Option 2: define name and address without var or val
class Employee (name: String, address: Address)
extends Person (name, address) { ... }

Because Scala has already generated the getter and setter methods for the name and address fields in the Person class, the solution is to declare the Employee constructor without var declarations:

// this is correct
class Employee (name: String, address: Address) extends Person (name, address) { ... }

Because you don’t declare the parameters in Employee as var, Scala won’t attempt to generate methods for those fields. You can demonstrate this by adding the Employee class definition to the code in Person.scala:

case class Address (city: String, state: String)

class Person (var name: String, var address: Address) {
    override def toString = if (address == null) name else s"$name @ $address"
}

class Employee (name: String, address: Address) extends Person (name, address) {
    // code here ...
}

Compiling the code with scalac and then disassembling the Employee.class file with javap, you see the following, expected result:

$ javap Employee
Compiled from "Person.scala"

public class Employee extends Person implements scala.ScalaObject{
    public Employee(java.lang.String, Address);
}

The Employee class extends Person, and Scala did not generate any methods for the name and address fields. Therefore, the Employee class inherits that behavior from Person.

While this example shows how Scala works with var fields, you can follow the same line of reasoning with val fields as well.