CHAPTER 3

image

Object Orientation in Scala

The essence of OOP is modelling abstractions for handling complexities in software development, by means of classes and objects. Finding the right abstractions, however, remains an arduous quest. An object in software is an abstraction of a real-world object, comprising essential properties and behaviors that set it apart from other objects. OOP is successful because object-oriented languages implement a number of principles such as encapsulation, inheritance, and polymorphism that make the software design and construction process much simpler and elegant when compared to other antiquated approaches. To illustrate these principles we concoct an abstraction with the aid of a classic example of shapes:

class Shape {
def area:Double = 0.0
}

We defined a supertype called Shape that has a method area that computes the area of the shape. We are not interested in every aspect of Shape. Instead, we center our attention on area of the shape. As far as one can tell, our choice, driven by the task we are trying to accomplish, is subjective. Based on your mathematical bent, you could choose other geometrical characteristics of shape involving circumference, major or minor axes, and so on. Affording this subjectivity is the corollary of the abstraction, so to speak. Now that we have abstraction in place, let’s discuss the three principles of OOP—encapsulation, inheritance, and polymorphism. We could have a myriad of shapes to subtype the Shape type we pick such as Rectangle and Circle as illustrated in Listing 3-1.

Listing 3-1. Concrete shapes

class Rectangle(val width:Double,val height:Double) extends Shape {
override def area:Double = width*height
}
class Circle(val radius:Double) extends Shape {
override def area:Double = math.Pi*radius*radius
}

Each of these classes takes some arguments and extends Shape, then overrides the method of Shape. A subtype is guaranteed to have all the members of the supertype. This is the mainstay of Inheritance. Although, you might not always want the members of subtype thus inherited to do the same things that members of supertype do and you can choose to change the implementation of the methods of the supertype. Changing the implementation of a method of the supertype is called overriding. One thing to note is that we cannot alter the width and the height of a Rectangle and the radius of the Circle objects because if the field is a val, as the fields width, height, and radius in our code are, Scala generates only a getter method for it. This is a trivial example of encapsulation. In encapsulation, the fields of an object are accessible only through its methods. In other words, one can either mutate or access the state of an object only by invoking its specific methods. This property is known as encapsulation. We can now write code that takes an instance of Shape and then pass to it an instance of either Rectangle or Circle:

def draw(s:Shape)

Now, consider two calls made to this function, like so:

val circle = draw(new Circle(3))
val rectangle = draw(new Rectangle(2,3))

The reason this works is that inheritance guarantees that any method we could call on an instance of Shape will be defined in the subtypes, that is, code that can work with multiple types. This is the heart of polymorphism. Because of polymorphism, we could create a new subtype of Shape at any point in the future and it would work with this code. Now that you’ve got the gist of the three principles of OOP let’s examine the parts that make the machine function.

Classes and Objects

A class is a blueprint for creating objects that are the concrete instances of a class. A class definition consists of field declarations and method definitions. Fields are used to store the state of an object and methods may provide access to fields, and alter the state of an object. Let’s start with a simple example of a blueprint for creating Book objects:

class Book

You can type this code on REPL to get the following output:

scala> class Book
defined class Book

The preceding Scala declaration that defined the Book class corresponds to this Java declaration:

public class Book {
}

You can confirm this by decompiling the class with the following javap command

scala> :javap -c Book
Compiled from "Book.scala"
public class Book {
  public Book();
    Code:
       0: aload_0
       1: invokespecial #12                 // Method java/lang/Object."<init>":()V
       4: return
}
scala>

Once you’ve defined a class, you can create objects from the class with the keyword new. To create an instance of Book, you can type the following:

new Book

But, this works just as well:

new Book()
scala> new Book
res0: Book = Book@181ba0
scala> new Book()
res1: Book = Book@d19e59
scala>

Constructors

A class consists of class members such as fields and methods. Fields hold the state of an object and are defined with either val or var. Methods complete the computational tasks of the object and are defined with keyword def. In Scala, the entire body of the class is the constructor. If the constructor takes zero parameters, you can omit the parameter list. In this section you will learn some different configurations of constructors with parameters.

Constructors with Parameters

Scala differentiates between a constructor declared with val fields, var fields, private val, or private var and fields without var or val.

Parameter Declared as a val

If the constructor parameter is declared as a val, Scala generates only a getter method for it. Note that only a getter method is generated, a setter method is not generated. Let’s declare a field as val as shown here:

class Book( val title:String)

You can now decompile this code. As you can see, Scala generates a title method. You can use this method to access the field.

scala> :javap -c Book
Compiled from "Book.scala"
public class Book {
  public java.lang.String title();
//.....
  public Book(java.lang.String);
//....
}

Because the constructor field is defined as a val, the value of the field is immutable by definition. Therefore, Scala generates only the getter method and no setter method.

scala> val book = new Book("Beginning Scala")
book: Book = Book@c6dd25
scala> book.title
res0: String = Beginning Scala
scala> book.title = "new title"
<console>:8: error: reassignment to val
       book.title = "new title"
                  ^

Image Note  In Scala, if the constructor or method takes zero parameters, you can omit the parameter list.

Parameter Declared as a var

As you might have guessed, if the constructor parameter is declared as a var, Scala generates both accessor and mutator methods.

class Book( var title:String)

If you decompile this code, you can see the generated mutator method with an unusual name, title_$eq.

scala> :javap -c Book
Compiled from "Book.scala"
public class Book {
  public java.lang.String title();
//.......
  public void title_$eq(java.lang.String);
//.........
  public Book(java.lang.String);
//........
}

So when you set the field, like so

book.title("new title")

Scala converts it to the following:

Book.title_$eq("Beginning Scala")

As you can see, you can mutate the field of Book object because it was declared with keyword var.

scala> val book = new Book("Beginning Scala")
book: Book = Book@19cfd87
scala> book.title = "new title"
book.title: String = new title
scala> book.title
res0: String = new title

Parameter Declared as a private val or var

You can add the private keyword to a val or var field to prevent getter and setter methods from being generated. In this case the fields could only be accessed from within members of the class:

class Book(private var title: String) {
def printTitle {println(title)}
 }
scala> val book = new Book("Beginning Scala")
book: Book = Book@1352aed
scala> book.title
<console>:9: error: variable title in class Book cannot be accessed in Book
              book.title
                   ^
scala> book.printTitle
Beginning Scala

Parameter Declared without val or var

Scala does not generate getter or setter when neither val nor var are specified on constructor parameters. As you can see here, you cannot access the field title of the Book.

scala> class Book(title: String)
defined class Book
scala> val book = new Book("Beginning Scala")
book: Book = Book@125b4a4c
scala> book.title
<console>:12: error: value title is not a member of Book
book.title
^

At first sight, parameter declared without val or var and the parameter declared as private val or private var behaves in the same manner. To appreciate the difference between the two, look at Listing 3-2.

Listing 3-2. Parameter with val

class Book(private val title: String) {
  def printTitle(b: Book) {
    println(b.title)
  }
}
scala> val book = new Book("Beginning Scala")
book: Book = Book@ea05be
scala> book.printTitle(new Book("Beginning Erlang"))
Beginning Erlang

As you can see in this case, you can change the title of the Book because the title is a private field that is accessible to this object and the other objects of Book. Now, the parameter of the constructor is declared without val or var as shown in Listing 3-3:

Listing 3-3. Parameter without val/var

class Book(title: String) {
  def printTitle(b: Book) {
    println(b.title)
  }
}

The title field is accessible only to this object as you can see in the output below.

>scalac Book.scala
Book.scala:3: error: value title is not a member of Book
    println(b.title)
              ^
one error found

Now that you have learned the different configurations of the constructor, you can provide a default value for a constructor parameter that gives other classes the option of specifying that parameter when calling the constructor.

Here’s a simple declaration of a Book class with one constructor parameter named title that has default value of “Beginning Scala”:

class Book (val title: String = "Beginning Scala")

Because the parameter is defined with a default value, you can call the constructor without specifying a title value:

scala> val book = new Book
book: Book = Book@4123cd31
scala> book.title
res0: String = Beginning Scala

You can also specify the title value of your choice when creating a new Book:

scala> val book = new Book("new title")
book: Book = Book@4cg3407d
scala> book.title
res1: String = new title

You can also choose to provide named parameters as shown in the following code:

scala> val book = new Book(title="Beginning Scala")
book: Book = Book@46aaf1d2
scala> book.title
res0: String = Beginning Scala

Auxiliary Constructor

You can define one or more auxiliary constructors for a class to provide different ways to create objects. Auxiliary constructors are defined by creating methods named this. In this way you can define multiple auxiliary constructors, but they must have different signatures. Each auxiliary constructor must begin with a call to a previously defined constructor. Listing 3-4 illustrates a primary constructor and two auxiliary constructors.

Listing 3-4. Auxiliary constructor

class Book (var title :String, var ISBN: Int) {
def this(title: String) {
this(title, 2222)
}
def this() {
this("Beginning Erlang")
this.ISBN = 1111
}
override def toString = s"$title ISBN- $ISBN"
}

Given these constructors, the same Book can be created in the following ways:

val book1 = new Book
val book2 = new Book("Beginning Clojure")
val book3 = new Book("Beginning Scala", 3333)
scala> val book1 = new Book
book1: Book = Beginning Erlang ISBN- 1111
scala> val book2  = new Book("Beginning Clojure")
book2: Book = Beginning Clojure ISBN- 2222
scala> val book3 = new Book("Beginning Scala", 3333)
book3: Book = Beginning Scala ISBN- 3333

An auxiliary constructor just needs to call one of the previously defined constructors. For instance, the auxiliary constructor that takes the title parameter calls this(title, ISBN) and the no-arg constructor this() calls this(title).

Method Declaration

Scala method declarations have the def keyword, the method name, parameters, optional return type, the = keyword, and the method body.myMethod takes no parameters and returns a String:

def myMethod():String = "Moof"

myOtherMethod takes no parameters and returns a String, but the return type is not explicitly declared because the compiler infers the return type. It is recommended to use type inferencing judiciously; if it’s not immediately obvious what the return type is, declare it explicitly.

def myOtherMethod() = "Moof"

You declare the parameters inside the method declaration’s parentheses. The parameter name must be followed by the parameter’s type:

def foo(a: Int):String = a.toString

You can declare multiple parameters:

def f2(a: Int, b:Boolean):String= if (b)a.toStringelse"false"

You can pass the type of a parameter or the return type as a parameter. The following code takes a parameter p and a type parameter T and returns a List of T. Thus, if you pass an Int, you’ll get a List[Int], and if you pass a String, you’ll get a List[String]. For the most part, the type inferencer calculates the type parameters so you don’t have to explicitly pass them.

scala> deflist[T](p:T):List[T] = p :: Nil
list: [T](T)List[T]

scala> list(1)
res2:List[Int]= List(1)
scala> list("Hello")
res3:List[java.lang.String] = List(Hello)

And the last parameter in the list may be repeated—a variable-length argument. If the last parameter is a variable-length argument, it is a Seq of the type of the variable-length argument, so in this case the as parameter is a Seq[Int]:

def largest(as: Int*): Int = as.reduceLeft((a, b)=> a maxb)

A variable-length argument method may be called as follows:

largest(1)
largest(2,3,99)
largest(33, 22,33,22)

You can mix type parameters with variable-length arguments:

def mkString[T](as: T*):String = as.foldLeft("")(_ + _.toString)

And you can put bounds on the type parameters. In this case, the types that are passed in must be Number or a subclass of Number:

def sum[T <:Number](as:T*): Double = as.foldLeft(0d)(_ + _.doubleValue)

Methods can be declared within any code scope, except at the top level, where classes, traits, and objects are declared. Methods can reference any variables in their scope as seen in Listing 3-5.

Listing 3-5. Methods can reference any variables in their scope

def readLines(br: BufferedReader) = {
var ret: List[String] = Nil

defreadAll():Unit= br.readLinematch {
case null =>
case s => ret ::= s ; readAll()
}

readAll()
ret.reverse
}

In this example, the readAll method is defined inside the scope of the readLines method. Thus, the readAll method has access to the variables br and ret because these variables are within the scope of the readLines method. The readAll method calls a method on br, and it updates ret, even though these variables are not explicitly passed to readAll. As we go through more examples in subsequent chapters, we’ll see how being able to use methods within methods and having access to all the variables in scope comes in handy.

Overriding methods in Scala is different than Java. Methods that override declared methods must include the override modifier. Methods that override abstract methods may include the override modifier as shown in Listing 3-6.

Listing 3-6. An abstract method without override

abstractclassBase {
defthing: String
}
classOneextends Base {
defthing= "Moof"
}

Methods that take no parameters and variables can be accessed the same way, and a val can override a def in a superclass as shown in Listing 3-7. This principle of uniform access turns out to be very useful.

Listing 3-7. Using override

classTwoextends One{
overridevalthing= (new java.util.Date).toString
}
classThree extends One{
overridelazy valthing= super.thing + (newjava.util.Date).toString
}

Code Blocks

Method and variable definitions can be single lines:

def meth9() = "Hello World"

Methods and variables also can be defined in code blocks that are denoted by curly braces:{ }. Code blocks may be nested. The result of a codeblock is the last line evaluated in the codeblock as shown in Listing 3-8.

Listing 3-8. The result of a codeblock

def meth3():String = {"Moof"}
def meth4():String = {
val d = new java.util.Date()
d.toString()
}

Variable definitions can be code blocks as well. This comes in handy when defining val variables, and the logic required to compute the value is non-trivial.

Listing 3-9. variable definition code block

val x3:String= {
val d = new java.util.Date()
d.toString()
}

Call-by-Name

In Java, all method invocations are call-by-reference or call-by-value (for primitive types). What this means is that the parameter’s value, or reference in the case of an AnyRef, is placed on the stack and passed to the callee. Scala gives you an additional mechanism for passing parameters to methods (and functions): call-by-name, which passes a code block to the callee. Each time the callee accesses the parameter, the code block is executed and the value is calculated. Call-by-name allows you to pass parameters that might take a longtime to calculate but may not be used. Call-by-name also allows you to create flow of control structures such as while, doWhile, and so on.

We declare a nano method, which prints a message and returns the current time with nano-second resolution:

Listing 3-10. Call by name

def nano() = {
println("Gettingnano")
System.nanoTime
}

Next, we declare the delayed method, which takes a call-by-name parameter by putting the => symbol between the variable name and the type. delayed prints a message demonstrating that the method has been entered. Next, delayed method prints a message with t’s value. Finally, delayed method returns t.

Listing 3-11. Call by name parameter

def delayed(t:=> Long) = {
println("Indelayed method")
println("Param:"+t)
t
}

Let’s see what happens when we call delayed with nano as a parameter:

scala> delayed(nano())
Indelayed method
Gettingnano
Param: 4475258994017
Gettingnano
res3:Long = 4475259694720

This indicates that delayed is entered before the call to nano and that nano is called twice. Let’s compare this to call-by-reference:

Listing 3-12. Call by reference

def notDelayed(t:Long) = {
println("Innotdelayed method")
println("Param:"+t)
t
}

Let’s try calling notDelayed:

scala> notDelayed(nano())
Gettingnano
Innotdelayed method
Param: 4513758999378
res4:Long = 4513758999378

nano is called before notDelayed is called because the parameter to notDelayed, nano, is calculated before notDelayed is called. This is the way Java programmers expect code to work.

Method Invocation

Scala provides a number of syntactic variations for invoking methods. There’s the standard Java dot notation:

instance.method()

But if a method does not take any parameters, the ending parentheses are optional:

instance.method

This allows methods without parameters methods to appear as properties or fields on the target instance. This results in more visually pleasing code.

Methods that take a single parameter can be invoked just as in Java:

instance.method(param)

But methods that take a single parameter can be invoked without dots or parentheses:

instance.method param

Because Scala allows method names to contain symbols such as +, -, *, and ?, Scala’s dotless method notation creates a syntactically neutral way of invoking methods that are hard-coded operators in Java.

scala> 2.1.*(4.3)
res5:Double = 9.03
scala> 2.1* 4.3
res6:Double = 9.03

Finally, you invoke multiparameter methods in Scala just as in Java:

instance.method(p1, p2)

If a Scala method takes a type parameter, typically, the type parameter will be inferred by the compiler, but you can also explicitly pass the type parameter:

instance.method[TypeParam](p1,p2)

Objects

In Scala, you can use object to refer to an instance of a class as in Java and you can also use object as a keyword. In this section you will see how to use object as a keyword.

Singleton Objects

Scala does not have static members. Instead, Scala has singleton objects. A singleton object definition looks like a class definition, except instead of the keyword class you use the keyword object. A singleton is a class that can have only one instance. For instance, you might create a singleton object to represent a Car like so:

object Car {
def drive { println("drive car") }
}

With Car defined as an object, there can be only one instance of it, and you can call its methods just like static methods on a Java class:

object Main extends App {
Car.drive
}

Unlike classes, singleton objects cannot take parameter. You can use singleton objects for many purposes, including collecting related utility methods, or defining an entry point to a Scala application. In Chapter 1 you used the object keyword to launch your application. There are two ways to create a launching point for your application: define an object with a properly defined main method or define an object or that extends the App trait. The first approach was shown in Chapter 1. For the second approach, define an object that extends the App trait as shown here:

object Hello extends App {
println("Hello, world")
}

Scala provides a trait, scala. Application that your singleton object should extend for launching the application. Then you place the code you would have put in the main method directly in the singleton object. Now you can compile and run this application. Note that in both approaches, Scala applications are launched from an object, not a class. We will discuss trait in a later section of this chapter and then look into it in detail in the Chapter 4.

Companion Objects

In Scala, both a class and an object can share the same name. When an object shares a name with a class, it’s called a companion object, and the class is called a companion class. A companion object is an object that shares the same name and source file with another class or trait. A trait could be seen as a Java interface. Using this approach lets you create static members on a class. The companion object is useful for implementing helper methods and factory. We want to implement a factory that creates different types of shapes. If we want to implement this shape factory in Scala, we will need only one source file. We use a companion class Shape and a companion object Shape, which acts as a factory.

Listing 3-13. Companion object

trait Shape {
  def area :Double
}

object Shape {
private class Circle(radius: Double) extends Shape{
    override val area = 3.14*radius*radius
  }

private class Rectangle (height: Double, length: Double)extends Shape{
    override val area = height * length
  }

  def apply(height :Double , length :Double ) : Shape = new Rectangle(height,length)
  def apply(radius :Double) : Shape = new Circle(radius)

}
scala> val circle = Shape(2)
circle: Shape = Shape$Circle@1675800
scala> circle.area
res0: Double = 12.56
scala> val rectangle = Shape(2,3)
rectangle: Shape = Shape$Rectangle@1276fd9
scala> rectangle.area
res2: Double = 6.0

A singleton object that does not share the same name with a companion class is called a standalone object.

Packaging and Imports

A package is a named module of code. Java and Scala convention dictates that package names are the reversed domain name of the code owner. For example, the packages in the Lift Web Framework (http://liftweb.net) begin with net.liftweb. Typically, the package also contains a descriptive name for the module. For example, the Lift utility package is net.liftweb.util. The package declaration is the first non-comment line in the source file:

package com.liftcode.stuff

Scala packages can be imported so that they can be referenced in the current compilation scope. The following statement imports the contents of the scala.xml package:

import scala.xml._

Import statements are made in the scope of prior imports. The following statement imports the scala.xml.transform package:

import transform._

You can import a single class and object (more on objects later in chapter), for example, HashMap from the scala.collection.mutable package:

import scala.collection.mutable.HashMap

You can import more than one class or object from a single package, for example, TreeMap and TreeSet from the scala.collection.immutable package:

import scala.collection.immutable.{TreeMap, TreeSet}

Finally, you can import a class or object and rename it. For example, you can import the JSON class/object from the scala.util.parsing.json package and rename it to JsonParser:

import scala.util.parsing.json.{JSON=> JsonParser}

import can be used inside any codeblock, and the import will be active only in the scope of that code block. For example, you can import something inside a class body as shown in Listing 3-14:

Listing 3-14. Using import inside class body

classFrog {
importscala.xml._
defn:NodeSeq= NodeSeq.Empty
}

Scala’s import statement can also import the methods of an object so that those methods can be used without explicit reference to the object that owns them. This is much like Java’s static import. Combining local scope import and importing objects allows you to fine-tune where the objects and their associated methods are imported as shown in Listing 3-15.

Listing 3-15. Combining local scope import andimporting objects

scala> objectMoose{
defbark = "woof"
}
definedmodule Moose

scala> importMoose._
import Moose._

scala> bark
res78:java.lang.String = woof

Inheritance

Scala supports single inheritance, not multiple inheritance. A child (or derived) class can have one and only one parent (or base) class. The sole exception is the root of the Scala class hierarchy, Any, which has no parent. You saw that classes in Scala are declared very much like Java classes, but can also have parameters. Let’s define a simple class that will be used to describe some aspects of inheritance as illustrated in Listing 3-16.

Listing 3-16. Vehicle class

class Vehicle (speed : Int){
val mph :Int = speed
    def race() = println("Racing")
}

The Vehicle class takes one argument, which is the speed of the vehicle. This argument must be passed when creating an instance of class Vehicle, as follows: new Vehicle(100). The class contains one method, called race.

Extending Class

Extending from a base class in Scala is similar to extending in Java except for two restrictions: method overriding requires the override keyword, and only the primary constructor can pass parameters to the base constructor.

It is possible to override methods inherited from a super class in Scala as illustrated in Listing 3-17:

Listing 3-17. Overriding methods inherited from a super class

class Car (speed : Int) extends Vehicle(speed) {
override val mph: Int= speed
override  def race() = println("Racing Car")
}

The class Car extends Vehicle class using the keyword extends. The field mph and the method race needs to be overridden using the keyword override.

Listing 3-18 shows another class Bike that extends Vehicle.

Listing 3-18. Vehicle hierarchy

class Vehicle (speed : Int){
val mph :Int = speed
    def race() = println("Racing")
}
class Car (speed : Int) extends Vehicle(speed) {
override val mph: Int= speed
override  def race() = println("Racing Car")

}
class Bike(speed : Int) extends Vehicle(speed) {
override val mph: Int = speed
override  def race() = println("Racing Bike")

}

Save Listing 3-18 in a file vehicle.scala and compile using:

>scalac vehicle.scala

Now you can enter the REPL using the scala command and create the vehicle object as shown here:

scala> val vehicle1 = new Car(200)

With this command, Scala creates the vehicle1 object as shown here:

vehicle1: Car = Car@19a8942

Now you can use this vehicle1 object created by Scala to access the speed of the Car:

scala> vehicle1.mph

Scala REPL emits the speed of the Car as shown here:

res1: Int = 200

In the similar manner, you can execute the race method of vehicle1:

scala>vehicle1.race()

Scala interpreter emits the output as shown here:

Racing Car

Now you can create the Bike object and access its property and method:

scala> val vehicle2 = new Bike(100)
vehicle2: Bike = Bike@b7ad3
scala>vehicle2.mph
res4: Int = 100
scala> vehicle2.race()
Racing Bike

Traits

Suppose you want to add another class to your vehicle hierarchy. This time you want to add a Batmobile. A Batmobile can race, glide, and fly. But you cannot add glide and fly methods to the Vehicle class because in a nonfictional world, Car and Bike that extend Vehicle do not glide or fly—not yet at least. So, in this case if you want to add Batmobile to your vehicle hierarchy, you can use a trait. Traits are like interfaces in Java, which can also contain code. In Scala, when a class inherits from a trait, it implements the interface of the trait, and inherits all the code contained in the trait. Listing 3-19 shows flying and gliding traits.

Listing 3-19. flying and gliding traits

trait flying {
    def fly() = println("flying")
}

trait floating gliding {
def gliding() = println("gliding")
}

Now you can create the Batmobile class that extends Vehicle class along with the flying and gliding traits, as shown in Listing 3-20.

Listing 3-20. Using with

class Batmobile(speed : Int) extends Vehicle(speed)  with flying with gliding{
override val mph: Int = speed
override  def race() = println("Racing Batmobile")
override def fly() = println("Flying Batmobile")
override def float() = println("Gliding Batmobile")

}

In Scala, traits can inherit classes. The keyword extends is also used when a class inherits a trait as its parent. The keyword extends is also used even when the class mixes in other traits using the with keyword. Also, extends is used when one trait is the child of another trait or class.

You can now create a Batmobile in the REPL as illustrated here:

scala> val vehicle3 = new Batmobile(300)
vehicle3: Batmobile = Batmobile@374ed5

Now you can access the fly method of the Batmobile shown here:

scala> vehicle3.fly()
Flying Batmobile

Now create a list of vehicles, then you can use the maxBy method provided by Scala collections library to find the fastest vehicle in the list.

scala> val vehicleList = List(vehicle1, vehicle2, vehicle3)
vehicleList: List[Vehicle] = List(Car@562791, Bike@e80317, Batmobile@374ed5)
scala> val fastestVehicle = vehicleList.maxBy(_.mph)
fastestVehicle: Vehicle = Batmobile@374ed5

Case Classes

Scala has a mechanism for creating classes that have the common stuff filled in. Most of the time, when we define a class, we have to write the toString, hashCode, and equals methods. These methods are boilerplate. Scala provides the case class mechanism for filling in these blanks, as well as support for pattern matching. A case class provides the same facilities as a normal class, but the compiler generates toString, hashCode, and equals methods (which you can override). Case classes can be instantiated without the use of the new statement. By default, all the parameters in the case class’s constructor become properties on the case class. Here’s how to create a case class:

case classStuff(name:String, age: Int)

You can create an instance of Stuff without the keyword new (you can use new if you want):

scala> vals = Stuff("David", 45)
s: Stuff = Stuff(David,45)

The case class’s to String method does the right thing:

scala> s.toString
res70:String = Stuff(David,45)

Stuff’s equals method does a deep comparison:

scala> s == Stuff("David",45)
res72:Boolean = true
scala> s == Stuff("David",43)
res73:Boolean = false

And the instance has properties:

scala> s.name
res74:String = David
scala> s.age
res75:Int = 45

If you want to write your own class that does the same thing as a case class does, it would look like Listing 3-21:

Listing 3-21. Implementing case features in a class on its own

classStuff(val name: String,valage: Int) {
overridedeftoString = "Stuff("+name+","+age+")"
overridedefhashCode= name.hashCode+ age
overridedefequals(other: AnyRef)= othermatch {
case s: Stuff=> this.name== s.name &&this.age == s.age
case _ => false
}
}

objectStuff {
defapply(name: String, age: Int) = newStuff(name,age)
defunapply(s: Stuff)= Some((s.name, s.age))
}

Case classes also come in handy for pattern matching, a topic we’ll explore in Chapter 6.

Value Classes

In Chapter 2, we described Scala type hierarchy where we showed Any class and its two children AnyRef and AnyVal. We explained that all user-defined classes written in Scala (or Java) extend AnyRef.

With value classes, Scala allows user-defined value classes that extend AnyVal, that is, value classes enable you to write classes on the AnyVal side of the Scala type hierarchy. Value classes are a new mechanism in Scala to avoid allocating runtime objects. Value classes allow you to add extension methods to a type without the runtime overhead of creating instances. This is accomplished through the definition of new AnyVal subclasses. The following illustrates a value class definition:

class SomeClass(val underlying: Int) extends AnyVal

The preceding SomeClass class has a single, public val parameter that is the underlying runtime representation. The type at compile time is SomeClass, but at runtime, the representation is an Int. A value class can define defs, but no vals, vars, or nested traits classes or objects. Listing 3-22 illustrates a def in the value class SomeClass.

Image Note  A value class can only extend a universal trait. We will explain this in Chapter 7.

Listing 3-22. Using def in the value class

    class SomeClass(val i: Int) extends AnyVal {
def twice() = i*2
}

Here SomeClass is a user-defined value class that wraps the Int parameter and encapsulates a twice method. To invoke the twice method, create the instance of the SomeClass class as follows:

scala> val v = new SomeClass(9)
v: SomeClass = SomeClass@9
scala> v.twice()
res5: Int = 18

At runtime the expression is optimized to the equivalent of a method class on a static object:

SomeClass.twice$extension(9)
You can check this by compiling SomeClass using scalac and then execute the following command:
javap –v SomeClass

Behind the scenes the Scala compiler generates a companion object for the value class and makes the v.twice() calls to the twice$extension method in the companion object. “$extension” is the suffix added to all the methods extracted from the companion class.

One use case for value classes is to combine them with implicit classes. Using an implicit class provides a more convenient syntax for defining extension methods, while value classes remove the runtime overhead. Implicit classes will be described in detail in Chapter 8.

Scala versus Java versus Ruby

Scala, Java, and Ruby are all object-oriented languages. They share many similarities and some differences. In this section, we’ll compare and contrast these popular languages.

Classes and Instances

Scala and Ruby are pure object-oriented languages. Everything in each language is an instance of a class. In Java, there are primitives and statics that are outside of the OO model. In Scala and Ruby, all operations on entities are via method calls. In Java, operators are treated differently and are not method calls. The uniformity of instances in Scala means that the developer does not have to perform special tests or to have different code paths to deal with primitive data types (int, char, long, and so on.) The following is legal in Scala:

scala> 1.hashCode
res0:Int = 1
scala> 2.toString
res1:java.lang.String = 2

You can define a method that takes a function that transforms an Int to an Int:

scala> defwith42(in: Int=> Int) = in(42)

and pass a function that is applying the +method to 33:

scala> with42(33+)
res4:Int = 75

At the language level, it’s very convenient and easy on the brain and the design to have everything be uniform. Scala and Ruby’s pure OO approach achieves this goal. As a side note, you may worry about performance. The Scala compiler optimizes operations on JVM primitives such that the performance of Scala code is nearly identical to the performance of Java code.

Traits, Interfaces, and Mixins

Every Java class, except Object, has a single superclass. Java classes may implement one or more interfaces. An interface is a contract that specifies the methods an implementing class must have. Java has interfaces. Interfaces define a contract for a given class. A class has zero or more interfaces. Interfaces define the methods that the class must implement. Parameters to a method call may be specifically defined as classes or interfaces. Interfaces provide a powerful mechanism for defining the contract that a given class must implement, requiring that a parameter to a method implement particular methods without specifying the concrete class of the parameter. This is the basis for dependency injection, using mocks in testing, and other abstraction patterns.

Scala has traits. Traits provide all the features of Java interfaces. However, Scala traits can contain method implementations and variables. Traits are a great way of implementing methods once and mixing those methods into all the classes that extend the trait.

Ruby has mixins, which are collections of methods that can be mixed into any class. Because Ruby does not have static typing and there is no way to declare the types of method parameters, there’s no reasonable way to use mixins to define a contract like interfaces. Ruby mixins provide a mechanism for composing code into classes but not a mechanism for defining or enforcing parameter types.

Object, Static, and Singletons

In Java, a class can have static methods and data. In this way, there is a single point of access to the method, and there’s no need to instantiate a class in order to access static methods. Static variables provide global access to the data across the JVM.

Scala provides a similar mechanism in the form of objects. Objects are implementations of the singleton pattern. There is one object instance per class loader. In this way, it’s possible to have globally shared state. However, objects adhere to Scala’s uniform OO model, and objects are instances of classes rather than some class-level constant. This allows objects to be passed as parameters.

Ruby has a singleton mixin that provides the singleton pattern in Ruby programs. In addition, Ruby also has class-level methods. In Ruby, you can add methods to the class. There is one instance of a class object per class in Ruby. You can add methods and properties to class objects, and those become globally available without instantiating an instance of the class. This provides another mechanism for sharing global state.

Functions, Anonymous Inner Classes, and Lambdas/Procs

The Java construct to pass units of computation as parameters to methods is anonymous inner classes. The use of anonymous inner classes was popularized with the Swing UI libraries. In Swing, most UI events are handled by interfaces that have one or two methods on them. The programmer passes the handlers by instantiating an anonymous inner class that has access to the private data of the enclosing class.

Scala’s functions are anonymous inner classes. Scala functions implement a uniform API with the apply method being the thing that’s invoked. The syntax for creating functions in Scala is much more economical than the three or four lines of boilerplate for creating anonymous inner classes in Java. Additionally, the rules for accessing variables in the local scope are more flexible in Scala. In Java, an anonymous inner class can only access final variables. In Scala, a function can access and mutate vars.

Ruby has a collection of overlapping features that allow passing blocks, procs, and lambdas as parameters to methods. These constructs have subtle differences in Ruby, but at their core they are chunks of code that reference variables in the scope in which they were created. Ruby also parses blocks such that blocks of code that are passed as parameters in method calls are syntactically identical to code blocks in while and if statements.

Scala has much in common with Ruby in terms of an object model and function passing. Scala has much in common with Java in terms of uniform access to the same code libraries and static typing. Scala has taken the best of both Java and Ruby and blended these things into a very cohesive whole.

Summary

This chapter took you through the OOP constructs of Scala. You learned how class and constructor definition in Scala differs from Java. You also learned several ways of configuring the constructor. You learned several new concepts such as traits and case classes. These concepts will be discussed in detail in subsequent chapters. In the next chapter you will learn the functional aspects of Scala.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset