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.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |