Mike Slinn

Classes Part 2

— Draft —

Published 2013-11-06. Last modified 2024-07-20.
Time to read: 2 minutes.

This lecture continues the exploration of Scala classes.

The sample code for this lecture can be found in courseNotes/src/main/scala/animals/Animals.scala.

Named Parameters

When reading code containing method calls with many parameters it is often hard to keep track which values are being supplied for various parameters. If multiple parameters have the same types (for example if several parameters were Booleans, Strings or Ints), it would be easy to mix up the order of the parameters.

That type of bug is very common, especially when there are a lot of parameters to specify. Scala allows all parameters for constructors and methods to be specified by their name; this permits the parameters to be specified in any order.

Unlike JavaScript named arguments and class constructors, Scala methods and constructors do not require any special preparation before they can be invoked using named parameters; all Scala methods and constructors accept named parameters.

Let’s define a Frog2 class. Like the Frog1 class in the preceding lecture, Frog2 extends Animal, and it adds a toString method which displays the properties of the object. The toString method uses string interpolation to mingle variable values with text.

Scala REPL
scala> class Frog2(val canSwim: Boolean,
                    val numLegs: Int,
                    val breathesAir: Boolean
                   ) extends Animal(numLegs, breathesAir) {
  override def toString = s"Frog2 canSwim=$canSwim, numLegs=$numLegs, breathesAir=$breathesAir"
}

If we create an instance of the Frog2 class without using named parameters, it is impossible to tell which boolean value corresponds to the canSwim or breathesAir parameters without looking at the class constructor signature. This is a common source of errors.

Scala REPL
scala> val frog2a = new Frog2(true, 4, true)
frog3: Frog2a = Frog2 canSwim=true, numLegs=4, breathesAir=true 

Lets create an instance of Frog2 using one named parameter. Because it is also defined as the first parameter and it is placed in its declared position, the parameter can be named without requiring all the other parameters to also be named.

Scala REPL
scala> val frog2b = new Frog2(canSwim=true, 4, true)
frog3: Frog2b = Frog2 canSwim=true, numLegs=4, breathesAir=true 

Even better, lets name all the boolean parameters.

Scala REPL
scala> val frog2c = new Frog2(canSwim=true, 4, breathesAir=true)
frog3: Frog2c = Frog2 canSwim=true, numLegs=4, breathesAir=true 

Parameter values do not need to be supplied in the order defined in the method signature if named parameters are used. This makes your code more robust.

The best approach is to name all parameters for every constructor and method invocation:

Scala REPL
scala> val tadpole = new Frog2(breathesAir=false, canSwim=true, numLegs=0)
tadpole: Frog2 = Frog2 canSwim=true, numLegs=0, breathesAir=false 

Optional Parameters

None of the Frog2 constructor parameters were defined with default values, so they all must be specified when the constructor is invoked.

Let’s examine the properties of tadpole1. Note that we can use tab completion to list all the properties and methods of the instance.

Scala REPL
scala> tadpole.Tab
asInstanceOf  breathesAir   canSwim       isInstanceOf  msg           numLegs       toString
scala>
tadpole.canSwim res0: Boolean = true

In Scala, parameters to methods can be designated as optional by providing default values. Let’s create a new class Frog3 which is like Frog2, but has default values for all parameters.

The rule is that if a method parameter has a default value, then all following parameters must also have default values.

Scala REPL
scala> class Frog3(canSwim: Boolean=true,
                   numLegs: Int=4,
                   breathesAir: Boolean=true
                  ) extends Animal(numLegs, breathesAir) {
  override def toString() = s"canSwim: $canSwim; $numLegs legs; breathesAir:$breathesAir"
}
// defined class Frog3
scala>
val frog3a = new Frog3 frog3a: Frog3 = canSwim: true; 4 legs; breathesAir: true
scala>
val frog3b = new Frog3(numLegs=2) frog3b: Frog3 = canSwim: true; 2 legs; breathesAir: true
scala>
val frog3c = new Frog3(false) frog3c: Frog3 = canSwim: false; 4 legs; breathesAir: true
scala>
val frog3d = new Frog3(false, breathesAir=false) frog3d: Frog3 = canSwim: false; 4 legs; breathesAir: false

Mutable Propoerties

Now let’s define a similar MutableFrog class with a mutable property, using the var keyword.

Scala REPL
scala> class MutableFrog(val canSwim: Boolean,
                   var numLegs: Int,
                   breathesAir: Boolean
                  ) extends Animal(numLegs, breathesAir) {
  override def toString = s"MutableFrog canSwim=$canSwim, numLegs=$numLegs, breathesAir=$breathesAir"
}
// defined class MutableFrog 

We can change the value of the mutable property:

Scala REPL
scala> val mutableFrog = new MutableFrog(canSwim=true, numLegs=4, breathesAir=true)
val mutableFrog: MutableFrog = MutableFrog canSwim=true, numLegs=4, breathesAir=true
scala>
mutableFrog.numLegs=2 mutableFrog.numLegs: Int = 2

... but not the immutable properties:

Scala REPL
scala> mutableFrog.canSwim=false
<console>:10: error: reassignment to val
  mutableFrog.canSwim=false 

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.