Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Introduction to Scala – creating Scala classes

2007, howto, scala

In the previous post, we explored how to use existing Java objects and classes. What if we want to create them in Scala instead?

It's possible to create classes with the class keyword, and a set of instance variables and instance methods. These are defined in the same way that we have done in the past, but just inside the scope of the class block:

scala> class Square {
     |   var x: Double = 0
     |   var y: Double = 0
     |   var width: Double = 0
     |   var height: Double = 0
     |   def area(): Double = width * height
     | }
defined class Square
scala> val small = new Square()
small: Square = Square@e45013
scala> { small.width = 1.5; small.height = 1.5; small.area() }
res1: Double = 2.25

We usually want to have a constructor, in order to make creating instances of the class easier. We can do this by using 'this' as a method name:

scala> class Square {
     |   var x: Double = 0
     |   var y: Double = 0
     |   var width: Double = 0
     |   var height: Double = 0
     |   def this(x: Double, y: Double) = { this(); this.x = x; this.y = y }
     |   def area(): Double = width * height
     | }

The pattern of constructor-populates-variables is so common, there's actually a pattern in Scala that enables you to create a primary constructor instead of the additional constructors that we've just seen. You can also use it to create variables instead of having to define them:

scala> class Square(
     |   var x: Double,
     |   var y: Double,
     |   var width: Double,
     |   var height: Double
     | ) {
     |   def area(): Double = width * height
     | }
defined class Square
scala> val small = new Square(0,0,1.5,1.5)
small: Square@9e14af
scala> small.area()
res2: Double = 2.25 

This creates a 4-arg constructor that also defines the instance variables, as well as accessor methods that permit us to access and change the value:

scala> { small.width = 3; small.area() }
res3: Double = 4.5

It's generally good practice to deal with immutable objects, if possible. We can do that with a couple of variations; we could make the variables private:

scala> class Square(
     |   private var x: Double,
     |   private var y: Double,
     |   private var width: Double,
     |   private var height: Double
     | ) {
     |   def area(): Double = width * height
     | }
defined class Square
scala> val small = new Square(0,0,1.5,1.5)
small: Square@6b80fa
scala> small.area()
res4: Double = 2.25 
scala> small.width = 2
<console>:9: error: variable width cannot be accessed in Square
scala> small.width
<console>:9: error: variable width cannot be accessed in Square

Unfortunately, that also makes the accessor private as well, so we lose the ability to both read and write to the members. Instead, we can make them values, so we don't have access to write at all:

scala> class Square(
     |   val x: Double,
     |   val y: Double,
     |   val width: Double,
     |   val height: Double
     | ) {
     |   def area(): Double = width * height
     | }
defined class Square
scala> val small = new Square(0,0,1.5,1.5)
small: Square@be4814
scala> small.area()
res5: Double = 2.25 
scala> small.width = 2
<console>:9: error: variable width cannot be accessed in Square
scala> small.width
res6: Double = 1.5

We might want to beautify the output (instead of Small@be4814) which is the default string representation. We'll do that by overriding the toString() method, and providing our own:

scala> class Square(
     |   val x: Double,
     |   val y: Double,
     |   val width: Double,
     |   val height: Double
     | ) {
     |   def area(): Double = width * height
     |   override def toString(): String = width + "x" + height + "@" + x + "," + y
     | }
defined class Square
scala> val small = new Square(0,0,1.5,1.5)
small: Square = 1.5x1.5@0.0,0.0

We need to use the override keyword here, to tell the compiler that we're overriding a method defined from a superclass. This helps catch a class of errors that exist when a superclass adds a method that the subclass hasn't expected. If you miss it out, and you're overriding a method, you get an error:

<console>:15: error: error overriding method toString in class Object of type ()java.lang.String;
 method toString needs 'override' modifier
        def toString(): String = width + "x" + height + "@" + x + "," + y

We've covered the basics of defining and using classes in this post. Next time, we'll visit the compiler, packages, and objects.