Published 2024-08-20.
Time to read: 8 minutes.
This lecture introduces Scala classes, a major contributor to its object-oriented capability, and explores the topic in depth.
The sample code for this lecture can be found in
courseNotes/
.
Scala’s object-oriented design is similar to Java. The same concept of classes and abstract classes exist. Java’s interfaces have been made more powerful by Scala’s traits, discussed in the Scala Traits lecture. Java 8’s interfaces are more powerful but somewhat similar. Scala replaces Java’s static fields and methods with the concept of a companion object.
Scala also has a convenient type of class definition, called a case class,
which is well-suited for modeling domain objects.
A case class is a regular class with a lot of methods added by default,
and an automatically defined companion object that is constructed with a standard set of methods.
The Objects
lecture discusses Scala object
s, and the
Case Classes
lecture discusses case classes.
Scala Classes
A class is a logical building block that can accept parameters for constructing instances of the class definition.
class MyClass(param1: String, param2: Int) { // body of class goes here, including the constructor }
Classes can contain methods and properties, as well as any other type of Scala artifact.
While classes are not required to accept constructor parameters, or to have methods or properties,
a properly designed class should have a constructor that accepts parameters for creating an instance of the class.
A class that does not accept constructor parameters should instead be defined as a trait,
because traits are more flexible than classes without constructor parameters,
or it should be defined as an singleton object
.
More on that later in this lecture.
A property is a public variable that is associated with an instance of a class; properties are either mutable or immutable, and might be read-only (so the property only has a getter – this is the default) or write-only (so the property only has a setter).
A method is a subroutine associated with an instance of a class that can be invoked in order to perform a computation.
Regular Classes Need a Body
Let’s start by defining a class called UselessAnimal
that
accepts two parameters to the constructor.
class UselessAnimal(numLegs: Int, breathesAir: Boolean)
This class is unusual and in fact useless because the parameters for the class
constructor (numLegs
and breathesAir
) are not used,
and then they are forgotten once the class is constructed. Parameters to the
class constructor are available throughout the class as private variables.
Since no body is defined for this class it has no constructor, no properties
and no methods, so the constructor parameters serve no purpose.
Case Classes use a similar syntax to what is shown above. They are often used for data models.
Useful Class
Here is a class definition that accepts the same constructor parameters as UselessAnimal
,
but also provides a class body that defines a public property (property1
)
and two methods (walk
and sing
).
Public properties default to being immutable, and act as if they had been declared as val
s.
Immutable public properties are just defined with a setter, and have no getter.
class UsefulAnimal(numLegs: Int, breathesAir: Boolean) { val property1 = s"I have $numLegs legs"
def walk(n: Int) = (if (numLegs==0) "slither " else if (numLegs==1) "hop " else if (numLegs==2) "stomp " else if (numLegs==4) "gallop " else "crawl ") * n
def sing = if (breathesAir) "La di da!" else "You must be joking!" }
We will learn how to write a multiway branch in the
Pattern Matching
lecture of the Intermediate Scala course.
For now, the series of if
statements above will suffice.
Using the REPL
Let’s explore how Scala classes work by using one of the Scala REPLs. The Scala compiler driver can create a REPL for us, just by mentioning its name:
$ scala Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25). Type in expressions to have them evaluated. Type :help for more information.
scala>

Now we can create an instance of the UsefulAnimal
class and play with it.
Like Java classes, Scala classes have an automatically generated toString
method,
which just prints out the name of the class and the memory address of the class instance.
The Scala REPL automatically invokes toString
whenever a new class instance is created:
scala> val monkey = new UsefulAnimal(2, true) monkey: UsefulAnimal = UsefulAnimal@31190526
scala> monkey.property1 res0: String = I have 2 legs
scala> monkey.walk(3) res2: String = "stomp stomp stomp"
scala> monkey.walk 3 res2: String = "stomp stomp stomp"
scala> monkey.sing res3: String = La di da!
Notice that the first time monkey.walk(3)
was computed, dot notation was used,
but the second time monkey.walk 3
was computed, infix notation was used.
The choice of the type of notation used is up to the programmer.

That was so much fun, let’s create another instance of
UsefulAnimal
and play with it also.
This instance will have zero legs, so it will slither
instead of stomp
ing.
Since breathesAir
is assigned the value false
,
we are not surprised to learn that fish cannot sing.
scala> val fish = new UsefulAnimal(0, false) fish: UsefulAnimal = UsefulAnimal@31ea9581
scala> fish.property1 res4: String = I have 0 legs
scala> fish.walk(4)
res5: String = "slither slither slither slither"
scala> fish.sing res6: String = You must be joking!
Notice that the two UsefulAnimal
instances are quite separate.
Their characteristics differ as a result of the values of the parameters pass
to the class constructor. Class instances share behavior as defined in the
class’s methods, however class instances do not share data between
themselves. We will discuss how individual class instances might have a
special relationship in the
Deceptively Familiar: Access Modifiers lecture.
Missing Parameters
This is what happens if you forget to specify one of the constructor parameter values.
scala> val oops = new UsefulAnimal(0) -- [E171] Type Error: ---------------------------------------------------------- 1 |val oops = new UsefulAnimal(0) | ^^^^^^^^^^^^^^^^^^^ |missing argument for parameter breathesAir of constructor UsefulAnimal in class UsefulAnimal: (numLegs: Int, breathesAir: Boolean): UsefulAnimal 1 error found
Primary Constructor
In Scala, all of the code in a class that is not part of a method definition or an inner class definition is part of the primary constructor. The primary constructor is not enclosed in a single method, as is the case with Java. The following shows a bad practice – spreading primary constructor code throughout a class:
class BadAnimal(numLegs: Int, breathesAir: Boolean) { val x = 3 // part of primary constructor println(breathesAir) // part of primary constructor // This is an example of a method definition, a getter, that uses string interpolation: def whatAmI = s"$breathesAir; $numLegs legs" val y = 3 // part of primary constructor }
The problem with the above is that you are never really sure if you have seen all of the primary constructor code unless you read every line of the class definition. Here is a better way of writing the same class:
class GoodAnimal(numLegs: Int, breathesAir: Boolean) { // The primary constructor consists of these 3 lines: val x = 3 println(breathesAir) val y = 3 // method definition, a getter: def whatAmI = s"$breathesAir;$numLegs legs" // Example of string interpolation }
The Auxiliary Constructors lecture is dedicated to the above topic.
Subclasses
We can create a specialized version of a class by defining a subclass. Subclasses inherit the properties and methods of their superclass.
We can derive another class from UsefulAnimal
that extends the definition in order to add methods and properties;
the resulting extended class definition is called a subclass, and the act of creating a class that extends another class is called subclassing.
Forwarding All Class Constructor Parameters to the Superclass Constructor
Notice how the parameters for the Kaiju
subclass constructor are
forwarded to the primary constructor of the superclass, which is UsefulAnimal
.
This is common, but we could provide other values to the superclass constructor as we will see shortly.
object Monster extends App { class Kaiju(numLegs: Int, breathesAir: Boolean) extends ConcreteClass.UsefulAnimal(numLegs, breathesAir) { var totalSteps = 0 override def walk(steps: Int): String = { totalSteps = steps + totalSteps "stomp " * steps } override def sing = "Roaarr!" } val gojira = new Kaiju(2, true) println("gojira.sing=${gojira.sing}") println("gojira.walk(10)=${gojira.walk(10)}") println("gojira.walk(5)=${gojira.walk(5)}") println("gojira.totalSteps=${gojira.totalSteps}") }
To run this code, type:
$ sbt "runMain Monster"

The Kaiju
class redefines, or overrides the walk
and sing
methods defined in the UsefulAnimal
superclass.
The Kaiju
class also defins a mutable public property called totalSteps
of type Int
that was not present in the superclass.
Now let’s create and exercise an instance of Kaiju
.
scala> val gojira = new Kaiju(2, true) gojira: Kaiju = Kaiju@6a400542
scala> gojira.sing res7: String = Roaarr!
scala> gojira.walk(10) stomp stomp stomp stomp stomp stomp stomp stomp stomp stomp
scala> gojira.walk(5) stomp stomp stomp stomp stomp
scala> gojira.totalSteps res8: Int = 15
You can query or set the value of the Kaiju
instance’s
totalSteps
property easily:
scala> gojira.totalSteps = 0 gojira.totalSteps: Int = 0
scala> gojira.totalSteps res9: Int = 0
Forwarding Some Class Constructor Parameters to the Superclass Constructor

We can forward the values of selected parameters from a subclass’s primary constructor to its superclass constructor, like this:
scala> class KingGhidorah(numLegs: Int, numHeads: Int) extends Kaiju(numLegs, true) defined class KingGhidorah
Notice that the numLegs
parameter for the KingGhidorah
subclass is forwarded to the
Kaiju
superclass’s constructor, while the breathesAir
parameter for the Kaiju
superclass is
always set true
.
All the other behavior and properties of the Kaiju superclass is inherited by the KingGhidorah
subclass:
scala> val kingGhidorah = new KingGhidorah(2, 3) kingGhidorah: KingGhidorah = KingGhidorah@336f1079
scala> kingGhidorah.walk(7) res10: String = "stomp stomp stomp stomp stomp stomp stomp"
scala> kingGhidorah.sing res11: String = Roaarr!
scala> kingGhidorah.totalSteps res12: Int = 7
Identical Instances Versus a Singleton

Let’s define a subclass of Kaiju
called Mothra
which does not forward any parameters to
the superclass constructor:
scala> class Mothra extends Kaiju(6, true) defined class Mothra
Notice that the extends
keyword is followed by an invocation of the Kaiju
superclass constructor, and constant values are passed
in for all of the Kaiju
constructor parameters.
The Mothra
subclass constructor does not accept any parameters,
so each time you create an instance of Mothra
you get an identical but distinct instance.
If you actually intended to define multiple identical instances (which would be unusual),
then defining a class that does not accept any parameters for its constructor is fine.
If, however, you really wanted to define the one and only Mothra
(a singleton)
then you should define a Scala object
, which we will discuss in the
Objects lecture.
Let’s create an instance of Mothra
and exercise it.
Behavior is as we expect:
scala> val mothra = new Mothra mothra: Mothra = Mothra@4d6025c5
scala> mothra.walk(5) stomp stomp stomp stomp stomp
scala> mothra.totalSteps res8: Int = 5
Abstract Classes

The above classes are called concrete classes because every declared method and property is defined.
An abstract class can contain declarations but not definitions for some or all properties or methods. An abstract class cannot be instantiated, instead, a concrete subclass that is derived from an abstract class can be instantiated.
DomesticAnimal
is an abstract class that declares a method (eat
) but does not define it,
and declares a public property called favoriteFood
that is also not defined.
Because the method signature for eat
is declared but no implementation for the method is defined,
this an abstract method.
Similarly, because only the favoriteFood
property’s name and type are declared, and no value is provided,
it is an abstract property.
Note that the toString
method must decorated with the override
keyword because this method definition replaces the
default toString
method that Scala automatically creates for every class.
abstract class DomesticAnimal(name: String, numLegs: Int, breathesAir: Boolean) extends UsefulAnimal(numLegs, breathesAir) { val favoriteFood: String
def eat(n: Int): String
override def toString = s"I am an animal called $name that likes to eat $favoriteFood like this: ${eat(3)}" }

We can define a concrete subclass of DomesticAnimal
by extending it and providing definitions for the
abstract members in the subclass.
class Horse(name: String) extends DomesticAnimal(name, 4, true) { val favoriteFood: String = "oats"
def eat(n: Int) = "Munch " * n + "!" }
When To Decorate With override
Notice that neither the favoriteFood
property nor the eat
method were decorated with the override
keyword.
This is because they merely provided initial definitions for the abstract class members,
and did not redefine a pre-existing definition.
We could preface these definitions with the override keyword if we wished, but that is a choice of personal style.
My own style is not to type any more characters than necessary.
Let’s create an instance of Horse
:
scala> new Horse("Bucephalus") // defined class Horse
Marking a Class Abstract
Abstract classes can define some or all properties and methods as abstract;
if all properties and methods are defined then only the abstract
keyword prevents the class from being
able to be instantiated.
Let’s define an abstract class called Animal
that uses the parameters to the constructor to compute
an immutable public property called msg
.
By decorating the class with the abstract
keyword,
only subclasses of Animal
can be instantiated.
Every declared method and property in this class is defined, however,
the programmer for some reason wished to prevent instances of Animal
from being
created and required that only subclasses could be defined and instantiated.
scala> abstract class Animal(numLegs: Int, breathesAir: Boolean) { | private val breatheMsg = if (breathesAir) "" else " do not" | val msg = s"I have $numLegs legs and I$breatheMsg breathe air" | } defined class Animal
Up to now, all classes have been defined in the top-level, or root package.
That is not desirable because you cannot access those definitions from other
packages. In the sample code, I placed the definition of
Animal
within the animals
package so we can refer to
it with the following import statement:
import animals.Animal
Class Properties

Let’s define a concrete class called Frog1
which extends the abstract Animal
class.
Decorating a Scala class constructor parameter with var
or val
causes it to also become a public property of the class.
Decorating with val
makes the property immutable, while decorating with var
makes it a mutable property.
Note how two of the new Frog1
class’s constructor parameters are forwarded to the abstract superclass’s constructor.
Frog1
also defines an immutable public property called canSwim
.
scala> class Frog1(var canSwim: Boolean, val numLegs: Int, breathesAir: Boolean) extends Animal(numLegs, breathesAir) defined class Frog1
You can create an instance of a Frog1
by passing in values for each constructor parameter.
scala> val frog1 = new Frog1(false, 2, true) frog1: Frog1 = Frog1@3b9e714c
You can query a property like this:
scala> frog1.canSwim res6: Boolean = true
Public properties declared as var
s are mutable, so they are
automatically defined with a setter and a getter. Let’s teach this frog to
swim:
scala> frog1.canSwim = true
Digging Deeper
Unless decorated with val
or var
, class constructor parameters are only accessible from within the class.
Constructors are special methods, and method parameters behave as if they are private variables within the enclosing scope.
For the primary constructor of a Scala class, the enclosing scope is the entire body of the class. For a method, the enclosing scope is the body of the method.

In the previous section I said that decorating a Scala class constructor argument with var
or val
causes it to become a public property of the class.
This is true whether or not the parameter is used within the body of the class.
If a constructor parameter is turned into a public property with the val
or var
keywords, and it is not used, it is still available outside the class.
To make this clear, try the following in a Scala REPL.
In class Macaw
, the size
and weight
constructor parameters are only accessible from inside
Macaw
class instances, while the age
is available inside and outside class instances:
scala> class Macaw(size: Double, weight: Double) { | val age: Int = 42 | println(s"Macaw: size=$size inches; weight=$weight pounds; age=$age years") | } defined class Macaw
scala> val macaw = new Macaw(32, 2.8) Macaw: size=32 inches; weight=2.8 pounds; age=42 years
scala> macaw.age res1: Int = 42 years
scala> macaw.size // does not compile <console>:10: error: value size is not a member of Macaw macaw.size ^

Even though the Toucan
class has no body, it is still useful for modeling domain objects because its
constructor parameters are decorated with the val
keyword so they become properties of the class:
scala> class Toucan(val size: Double, val weight: Double, val billLength: Double) defined class Toucan
scala> val toucan = new Toucan(27, 3.7, 9.3) toucan: Toucan = Toucan2@4ed5eb72
scala> toucan.size res3: Double = 27
scala> toucan.weight res8: Double = 3.7
scala> toucan.billLength res9: Double: 9.3
Notice what happens if we forget to decorate one of the parameters with the val
keyword:
scala> class Toucan2(val size: Double, val weight: Double, <>billLength: Double) defined class Toucan
scala> val toucan2 = new Toucan2(27, 3.7, 9.3) toucan: Toucan2 = Toucan2@4ed5eb72
scala> toucan2.size res3: Double = 27
scala> toucan2.weight res8: Double = 3.7
scala> toucan2.billLength // this parameter was not decorated with val so it is not a property <console>:11: error: value billLength is not a member of Toucan toucan.billLength
Exercise – Subclassing Animal

Define a new Animal
subclass called Bird
.
Give it a Boolean
constructor property called canFly
,
and another Double
property called topSpeed
.
Create a few Bird
instances with different top speeds.
Hint: If the class you defined for your solution resides in a different package than the package that Animal
was defined in,
don’t forget to import animals.Animal
:
import animals.Animal
Solution
This solution provides the constants 2
and true
to the Animal
superclass constructor.
package solutions
import animals.Animal
class Bird(val canFly: Boolean, val topSpeed: Double) extends Animal2(2, true) { override def toString = s"canFly=$canFly; topSpeed=$topSpeed" }
object Birds1 extends App { val crane = new Bird(true, 55) val emu = new Bird(false, 25) println(s"Crane $crane") println(s"Emu $emu") }
This solution is provided in src/main/scala/solutions/Bird1.scala
.
Don’t look at Bird2.scala
– that is for a later exercise.
You can run this solution by typing:
$ sbt "runMain solutions.Birds1"
Crane canFly=true; topSpeed=55.0
Emu canFly=false; topSpeed=25.0
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.