Scala Tutorials Part #5 - Classes


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

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
PublicYYYY
ProtectedYYYN
Default/No modifierYYNN
PrivateYNNN

A similar representation can be drawn for scala.

Modifier Class Companion Object Package Subclass Project
Default/No modifierYYYYY
ProtectedYYN *YN
PrivateYYN *NN

* 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.

Constructor no value

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 creation 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.

Val constructors

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.

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.

Override Error

This promotes better type safety and accidental overriding.

We can add a override keyword in front of the method to work as expected.

Override no error

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.

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


Tagged Under


Scala


Search this website...




Keeping up with blogs...

I blog occasionally and mostly write about Software engineering and the likes. If you are interested in keeping up with new blog posts, you should follow me on twitter where I usually tweet when I publish them. You can also use the RSS feed , or even subscribe via email below.

Feedio Subscribe


Share & Like

If you like this post, then you can either share/discuss/vote up on the below sites.



Thoughts ...

Please feel free to share your comments below for discussion

Blog comments powered by Disqus