Scala Programming Projects
上QQ阅读APP看书,第一时间看更新

Class

We mentioned earlier that all Scala expressions have a type. A class is a sort of template that can create objects of a specific type. When we want to obtain a value of a certain type, we can instantiate a new object using new followed by the class name:

scala> class Robot
defined class Robot

scala> val nao = new Robot
nao: Robot = Robot@78318ac2

The instantiation of an object allocates a portion of heap memory in the JVM. In the preceding example, the value nao is actually a reference to the portion of heap memory that keeps the content of our new Robot object. You can observe that when the Scala console printed the variable nao, it outputted the name of the class, followed by @78318ac2. This hexadecimal number is, in fact, the memory address of where the object is stored in the heap.

The eq operator can be handy to check if two references are equal. If they are equal, this means that they point to the same portion of memory:

scala> val naoBis = nao
naoBis: Robot = Robot@78318ac2

scala> nao eq naoBis
res0: Boolean = true

scala> val johnny5 = new Robot
johnny5: Robot = Robot@6b64bf61

scala> nao eq johnny5
res1: Boolean = false

A class can have zero to many members. A member can be either:

  • An attribute, also called a field. It is a variable whose content is unique to each instance of the class.
  • A method. This is a function that can read and/or write the attributes of the instance. It can have additional parameters.

Here is a class that defines a few members:

scala> class Rectangle(width: Int, height: Int) {
val area: Int = width * height
def scale(factor: Int): Rectangle = new Rectangle(width * factor, height * factor)
}
defined class Rectangle

The attributes declared inside the brackets () are a bit special: they are constructor arguments, which means that their value must be specified when we instantiate a new object of the class. The other members must be defined inside the curly brackets {}. In our example, we defined four members:

  • Two attributes that are constructor arguments: width and height.
  • One attribute, area. Its value is defined when an instance is created by using the other attributes.
  • One method, scale, which uses the attributes to create a new instance of the class Rectangle.

You can call a member on an instance of a class by using the postfix notation myInstance.member. Let's create a few instances of our class and try to call the members:

scala> val square = new Rectangle(2, 2)
square: Rectangle = Rectangle@2af9a5ef

scala> square.area
res0: Int = 4

scala> val square2 = square.scale(2)
square2: Rectangle = Rectangle@8d29719

scala> square2.area
res1: Int = 16

scala> square.width
<console>:13: error: value width is not a member of Rectangle
square.width

We can call the members area and scale, but not width. Why is that?

This is because, by default, constructor arguments are not accessible from the outside world. They are private to the instance and can only be accessed from the other members. If you want to make the constructor arguments accessible, you need to prefix them with val:

scala> class Rectangle(val width: Int, val height: Int) {
val area: Int = width * height
def scale(factor: Int): Rectangle = new Rectangle(width * factor, height * factor)
}
defined class Rectangle

scala> val rect = new Rectangle(3, 2)
rect: Rectangle = Rectangle@3dbb7bb

scala> rect.width
res3: Int = 3

scala> rect.height
res4: Int = 2

This time, we can get access to the constructor arguments. Note that you can declare attributes using var instead of val. This would make your attribute modifiable. However, in functional programming, we avoid mutating variables. A var attribute in a class is something that should be used cautiously in specific situations. An experienced Scala programmer would flag it immediately in a code review and its usage should be always justified in a code comment.

If you need to modify an attribute, it is better to return a new instance of the class with the modified attribute, as we did in the preceding Rectangle.scale method.

You might worry that all these new objects will consume too much memory. Fortunately, the JVM has a mechanism known as the garbage collector. It automatically frees up the memory used by objects that are not referenced by any variable.