Scala Tutorials Part #5 - Classes
Originally Posted On : 17 Nov 2016 Last Updated : 18 Oct 2017
Classes
This is part 5 of the scala tutorial series. Check here for the full series.
: This article has been translated to chinese by ChanZong Huang, you can check it out here
Index
- Introduction
- Access and visibility
- Constructors
- Class parameters and Class fields
- Promoting class parameters
- Direct member access
- Immutable objects and Mutable objects
- When to use Getters and Setters
- Scala style Getters and Setters
- Auxiliary constructors
- Default constructor values
- Named arguments
- Abstract classes
- The Override keyword
- When to use abstract classes
Introduction
The concept of a class in scala is very similar to their counterpart in java. But there are lot of differences in the functionality they provide.
To start off, let’s take a simple example.
//Typical java style class
class Person {
var name = "Noname"
var age = -1
def setName (name:String) {
this.name = name
}
def setAge (age:Int) {
this.age = age
}
def getName () : String = {
name
}
def getAge () : Int = {
age
}
}
We have two variables name
and age
with getters and setters which can be accessed as follows.
val personObj = new Person
println(personObj.getName)
println(personObj.getAge)
Access and visibility
Previous example had the default access modifier. Let’s look at other modifiers in detail.
To recap, java has four access modifiers.
Java access levels
Modifier | Class | Package | Subclass | Project |
---|---|---|---|---|
Public | Y | Y | Y | Y |
Protected | Y | Y | Y | N |
Default/No modifier | Y | Y | N | N |
Private | Y | N | N | N |
A similar representation can be drawn for scala.
Modifier | Class | Companion Object | Package | Subclass | Project |
---|---|---|---|---|---|
Default/No modifier | Y | Y | Y | Y | Y |
Protected | Y | Y | N * | Y | N |
Private | Y | Y | N * | N | N |
*
Top level protected
and private
members can be accessed from inside a package
It is more or less similar to java’s access modifiers, except that there are only three levels. There is no equivalent of java’s default access, and the default in scala is equal to java’s public.
This should be enough to get going, but if you are interested in taking a more deeper look, scala access modifiers is a good read.
The official documentation can also be referred.
You would find that, the scala access levels are slightly better in contrast to java.
Constructors
Next obvious thing to understand in a class would be constructors. Unlike java, there are some unique ways to create constructors.
class Person (name:String,age:Int) {
def getName () : String = {
name
}
def getAge () : Int = {
age
}
}
The class implementation is similar to the original one, except that there are no setters and only getters.
They can be consumed as below.
val personObj = new Person("John",-1)
println(personObj.getName)
println(personObj.getAge)
Method style parameters are given during class definition to indicate constructors.
Once a constructor is declared, we cannot create a class without providing the default parameters as specified in the constructor.
We cannot call the variables directly as they resort to private val
by default and as per the access levels defined above, it would throw an error.
Class parameters and Class fields
It is important to understand that for a class, there are two components i.e class parameters and class fields.
Class parameters are one which you mention in declaration i.e the primary constructor.
Class field are regular member variables.
The above example had getters, without those it would be impossible for another class to access the variables of that particular class.
If we take a closer look, the actual reason is more subtle than just private, the parameters have scope only to the constructor and do not live beyond that. This is very similar to accessing a variable that is declared inside a loop. These are class parameters and live only inside the scope of the constructor.
Class fields on the other hand can be accessed (based on their access level) outside the class.
This distinction is important and it forms the basis of the following discussions.
Promoting class parameters
The process of promoting class parameters is nothing but changing their scope for usage beyond the constructor.
This can be done in two ways. Either by affixing val
or var
before the variables in the constructor parameters.
class Person (val name:String,val age:Int) {}
or
class Person (var name:String,var age:Int) {}
Even though the classes do not have a body, instances can still be created and consumed.
Direct member access
Changing the parameters to val/var enables us to directly access the class variables, without a getter/setter.
The above example with either val/var can be consumed as below.
val example = new Person("Test",20)
println(example.name)
println(example.age)
We can access the variables directly since it is the default access level. In fact, val/var do not have anything to do with access.
Designing things like this is a bad idea, but it is something that can be done.
This gives rise to two ways in which a class can be designed i.e mutable/immutable.
Immutable objects and Mutable objects
With val, we can have immutable objects.
Immutable objects
We can declare the variables as immutable in class constructors.
class Person (val name:String,val age:Int) {
def getName () : String = {
name
}
def getAge () : Int = {
age
}
}
Once the values are declared, it cannot be re-assigned again, this is the default behaviour of val as explored in the first part.
Scala has something called case classes
which are specifically built for handling situations like immutable classes along with several other neat features.
We will be exploring cases classes in a future tutorial.
Mutable objects
class Person (var name:String,var age:Int) {
def getName () : String = {
name
}
def getAge () : Int = {
age
}
}
We can then declare an instance and change the name directly.
val p = new Person("Rob",43)
p.name = "Jacob"
//Prints the changed name
println(p.name)
When to use Getters and Setters
There are two key decisions to be made when designing a class.
- Whether it should be mutable/immutable (Read objects should be immutable)
- Using getters/setters vs direct variable access.
For the first point, it is basically trade offs. The linked article summarizes the advantages of immutable objects.
The second point is something to ponder over.
Traditional wisdom in java on when to use getters and setters still apply.
A simple example is to do more than just setting values to the variable, we can do several other things such as checking access, logging etc.,
But the scala style guide says a different story.
What is being advised is that the users of the class need not have knowledge on whether a method/class member is being accessed. They can simply choose to change/access it with the variable name itself. This greatly simplifies code and it looks much cleaner.
The next section describes on how to create such a getter/setter with a somewhat obscure syntax.
Scala style Getters and Setters
Scala has a different way of creating getters/setters although the java style is still supported as we saw in the first example.
Lets take the below class.
class Person() {
private var _age: Int = -1
def age: Int = _age
def age_=(value: Int) = {
_age = value
}
}
This can be consumed as below.
val p = new Person
p.age = 20
println(p.age)
Parameter age
is actually a method inside the class Person, however the user can access this as if it was a variable.
The syntax might look bizarre, but understanding where they fit in will give a more clear picture. Lets take it apart piece by piece.
age
is actually _age
behind the scenes. The getter is pretty clear, just return the _age
variable.
The setter is little more confusing. First the method name is def age_=
, the underscore is a special character which enables a space in calling the method.
This enables us to call the method like age =
.
Notice that the method name, underscore and the equal sign should be together without any space.
Everything else should make sense already i.e it takes in a parameter value
and assigns it to the _age
variable.
This definition should be enough to get your head around it, but the underscore character has more to it and there is something called the uniform access principle. We will explore that in later dedicated tutorial.
Scala plugin in Intellij can do all the code generation for you.
Getters and setters in general are viewed as second class citizens since scala encourages immutable objects.
Auxiliary constructors
In a real world scenario we often need to have two or three constructors with different parameters.
To do this we have a special style of constructor creation called auxiliary constructors.
class Person (name:String,age:Int) {
//When nothing is provided
def this(){
this("",-1)
}
//When name is provided, but age is not
def this(name:String){
this(name,-1)
}
//When age is provided, but name is not
def this(age:Int){
this("",age)
}
def getName () : String = {
name
}
def getAge () : Int = {
age
}
}
The comments on the code pretty much sums up the idea. An important thing to note is that an auxiliary constructor is not an actual constructor, but acts as a wrapper to the existing constructor.
When we want to add a parameter to the above person class, say a phone number, then we add it to the primary constructor i.e the class parameters.
class Person (name:String,age:Int,phone:String) {
//When nothing is provided
def this(){
this("",-1,"")
}
//When name is provided, but age is not
def this(name:String){
this(name,-1,"")
}
//When age is provided, but name is not
def this(age:Int){
this("",age,"")
}
def getName () : String = {
name
}
def getAge () : Int = {
age
}
}
Please note that the all of the auxiliary constructors have to now include the phone as a parameter. This might seem as an overhead, but it is actually good design.
Default constructor values
Auxiliary constructors are good for implementing polymorphic behaviour, i.e different constructors can exhibit different flows in the class execution/functionality.
A most common bad practice is to use these to implement default variable values.
Like methods, we can provide the default values during constructor declaration itself.
class Person(name:String = "Unnamed",age:Int = -1,phone:String = "No number") {
def getName () : String = {
name
}
def getAge () : Int = {
age
}
def getPhone () : String ={
phone
}
}
We can then consumer this class without declaring value to one or even all of them.
val example = new Person()
println(example.getName())
println(example.getAge())
println(example.getPhone())
Listed example will work correctly and would print the default values Unnamed,-1,No number.
This is a pretty handy way of declaring pre-defined class values.
Named arguments
We can create classes with only specific variables as user defined and leaving the rest to the class defaults.
class Person(val name:String = "Unnamed",val age:Int = -1,val phone:String = "No number")
If we want to give only the name
and leave the rest of the variables as defaults then,
val personDemo = new Person(name = "John Ruckus")
println(personDemo.name) //Prints John Ruckus
println(personDemo.age) //Prints -1
println(personDemo.phone) //Prints No number
Abstract classes
Abstract classes in scala are similar to java, but they are superseded by a concept called traits
. We will explore traits in a later tutorial.
Below is a simple example of an abstract class.
abstract class Parent(name:String = "Noname") {
//Method with no definition/return type
def getAge
//Method with no definition and String return type
def getID : String
//Explicit way of saying that no implementation is present
def getEmail () : String {}
//Method with its functionality implemented
//Need not be implemented by subclasses, can be overridden if required
def getName : String = {
name
}
}
The comments give a good idea of what the methods do. The abstract
keyword is not necessary, when the method body is not defined it is taken to be as an abstract method.
If we extend this to another class, we need to implement the three methods getAge
,getID
and getEmail
.
We may choose to override/not override the getName
method since it is already implemented.
The Override keyword
In Java when we need to override a method from a parent class we need to do anything special. For clarity we can use the @Override
annotation.
Scala has an override
keyword which is mandatory in case of overriding a concrete method from a parent class.
This promotes better type safety and accidental overriding.
We can add a override
keyword in front of the method to work as expected.
When to use abstract classes
As I said before, abstract classes were superseded by traits
, so in what situation we need to use Abstract classes ?
This requires more knowledge of traits, but we will briefly touch upon two situations.
-
When we want a base class with constructor arguments
We will see in further tutorials why traits do not allow this.
-
When we want to use the class from Java code
Since traits are something specific to scala, abstract classes are better in terms of compatibility.
With this, I would like to bring an end to this tutorial. The next in this series would be understanding case classes, traits and why they are really cool.
Stay tuned