Chapter 6. Advanced Object-Oriented Programming In Scala

We’ve got the basics of OOP in Scala under our belt, but there’s plenty more to learn.

Overriding Members of Classes and Traits

Classes and traits can declare abstract members: fields, methods, and types. These members must be defined by a derived class or trait before an instance can be created. Most object-oriented languages support abstract methods, and some also support abstract fields and types.

Note

When overriding a concrete member, Scala requires the override keyword. It is optional when a subtype defines (“overrides”) an abstract member. Conversely, don’t use override unless you are actually overriding a member.

Requiring the override keyword has several benefits:

  • It catches misspelled members that were intended to be overrides. The compiler will throw an error that the member doesn’t override anything.

  • It catches a potentially subtle bug that can occur if a new member is added to a base class where the member’s name collides with an older derived class member that is unknown to the base class developer. That is, the derived-class member was never intended to override a base-class member. Because the derived class member won’t have the override keyword, the compiler will throw an error when the new base-class member is introduced.

  • Having to add the keyword reminds you to consider what members should or should not be overridden.

Java has an optional @Override annotation for methods. It helps catch errors of the first type (misspellings), but it can’t help with errors of the second type, since using the annotation is optional.

Attempting to Override final Declarations

However, if a declaration includes the final keyword, then overriding the declaration is prohibited. In the following example, the fixedMethod is declared final in the parent class. Attempting to compile the example will result in a compilation error:

// code-examples/AdvOOP/overrides/final-member-wont-compile.scala
// WON'T COMPILE.

class NotFixed {
  final def fixedMethod = "fixed"
}

class Changeable2 extends NotFixed {
  override def fixedMethod = "not fixed"   // ERROR
}

This constraint applies to classes and traits as well as members. In this example, the class Fixed is declared final, so an attempt to derive a new type from it will also fail to compile:

// code-examples/AdvOOP/overrides/final-class-wont-compile.scala
// WON'T COMPILE.

final class Fixed {
  def doSomething = "Fixed did something!"
}

class Changeable1 extends Fixed     // ERROR

Note

Some of the types in the Scala library are final, including JDK classes like String and all the “value” types derived from AnyVal (see The Scala Type Hierarchy).

For declarations that aren’t final, let’s examine the rules and behaviors for overriding, starting with methods.

Overriding Abstract and Concrete Methods

Let’s extend our familiar Widget base class with an abstract method draw, to support “rendering” the widget to a display, web page, etc. We’ll also override a concrete method familiar to any Java programmer, toString(), using an ad hoc format. As before, we will use a new package, ui3.

Note

Drawing is actually a cross-cutting concern. The state of a Widget is one thing; how it is rendered on different platforms, thick clients, web pages, mobile devices, etc., is a separate issue. So, drawing is a very good candidate for a trait, especially if you want your GUI abstractions to be portable. However, to keep things simple, we will handle drawing in the Widget hierarchy itself.

Here is the revised Widget class, with draw and toString methods:

// code-examples/AdvOOP/ui3/widget.scala

package ui3

abstract class Widget {
  def draw(): Unit
  override def toString() = "(widget)"
}

The draw method is abstract because it has no body; that is, the method isn’t followed by an equals sign (=), nor any text after it. Therefore, Widget has to be declared abstract (it was optional before). Each concrete subclass of Widget will have to implement draw or rely on a parent class that implements it. We don’t need to return anything from draw, so its return value is Unit.

The toString() method is straightforward. Since AnyRef defines toString, the override keyword is required for Widget.toString.

Here is the revised Button class, with draw and toString methods:

// code-examples/AdvOOP/ui3/button.scala

package ui3

class Button(val label: String) extends Widget with Clickable {

  def click() = {
    // Logic to give the appearance of clicking a button...
  }

  def draw() = {
    // Logic to draw the button on the display, web page, etc.
  }

  override def toString() =
    "(button: label=" + label + ", " + super.toString() + ")"
}

Button implements the abstract method draw. No override keyword is required. Button also overrides toString, so the override keyword is required. Note that super.toString is called.

The super keyword is analogous to this, but it binds to the parent type, which is the aggregation of the parent class and any mixed-in traits. The search for super.toString will find the “closest” parent type toString, as determined by the linearization process (see Linearization of an Object’s Hierarchy). In this case, since Clickable doesn’t define toString, Widget.toString will be called.

Tip

Overriding a concrete method should be done rarely, because it is error-prone. Should you invoke the parent method? If so, when? Do you call it before doing anything else, or afterward? While the writer of the parent method might document the overriding constraints for the method, it’s difficult to ensure that the writer of a derived class will honor those constraints. A much more robust approach is the Template Method Pattern (see [GOF1995]).

Overriding Abstract and Concrete Fields

Most object-oriented languages allow you to override mutable fields (var). Fewer OO languages allow you to define abstract fields or override concrete immutable fields (val). For example, it’s common for a base class constructor to initialize a mutable field and for a derived class constructor to change its value.

We’ll discuss overriding fields in traits and classes separately, as traits have some particular issues.

Overriding Abstract and Concrete Fields in Traits

Recall our VetoableClicks trait in Stackable Traits. It defines a val named maxAllowed and initializes it to 1. We would like the ability to override the value in a class that mixes in this trait.

Unfortunately, in Scala version 2.7.X, it is not possible to override a val defined in a trait. However it is possible to override a val defined in a parent class. Version 2.8 of Scala does support overriding a val in a trait.

Tip

Because the override behavior for a val in a trait is changing, you should avoid relying on the ability to override it, if you are currently using Scala version 2.7.X. Use another approach instead.

Unfortunately, the version 2.7 compiler accepts code that attempts to override a trait-defined val, but the override does not actually happen, as illustrated by this example:

// code-examples/AdvOOP/overrides/trait-val-script.scala
// DANGER! Silent failure to override a trait's "name" (V2.7.5 only).
// Works as expected in V2.8.0.

trait T1 {
  val name = "T1"
}

class Base

class ClassWithT1 extends Base with T1 {
  override val name = "ClassWithT1"
}

val c = new ClassWithT1()
println(c.name)

class ClassExtendsT1 extends T1 {
  override val name = "ClassExtendsT1"
}

val c2 = new ClassExtendsT1()
println(c2.name)

If you run this script with scala version 2.7.5, the output is the following:

T1
T1

Reading the script, we would have expected the two T1 strings to be ClassWithT1 and ClassExtendsT1, respectively.

However, if you run this script with scala version 2.8.0, you get this output:

ClassWithT1
ClassExtendsT1

Caution

Attempts to override a trait-defined val will be accepted by the compiler, but have no effect in Scala version 2.7.X.

There are three workarounds you can use with Scala version 2.7. The first is to use some advanced options for scala and scalac. The -Xfuture option will enable the override behavior supported in version 2.8. The -Xcheckinit option will analyze your code and report whether the behavior change will break it. The option -Xexperimental, which enables many experimental changes, will also warn you that the val override behavior is different.

The second workaround is to make the val abstract in the trait. This forces an instance using the trait to assign a value. Declaring a val in a trait abstract is a perfectly useful design approach for both versions of Scala. In fact, this will be the best design choice, when there is no appropriate default value to assign to the val in the trait:

// code-examples/AdvOOP/overrides/trait-abs-val-script.scala

trait AbstractT1 {
  val name: String
}

class Base

class ClassWithAbstractT1 extends Base with AbstractT1 {
  val name = "ClassWithAbstractT1"
}

val c = new ClassWithAbstractT1()
println(c.name)

class ClassExtendsAbstractT1 extends AbstractT1 {
  val name = "ClassExtendsAbstractT1"
}

val c2 = new ClassExtendsAbstractT1()
println(c2.name)

This script produces the output that we would expect:

ClassWithAbstractT1
ClassExtendsAbstractT1

So, an abstract val works fine, unless the field is used in the trait body in a way that will fail until the field is properly initialized. Unfortunately, the proper initialization won’t occur until after the trait’s body has executed. Consider the following example:

// code-examples/AdvOOP/overrides/trait-invalid-init-val-script.scala
// ERROR: "value" read before initialized.

trait AbstractT2 {
  println("In AbstractT2:")
  val value: Int
  val inverse = 1.0/value      // ???
  println("AbstractT2: value = "+value+", inverse = "+inverse)
}

val c2b = new AbstractT2 {
  println("In c2b:")
  val value = 10
}
println("c2b.value = "+c2b.value+", inverse = "+c2b.inverse)

While it appears that we are creating an instance of the trait with new AbstractT2 ..., we are actually using an anonymous class that implicitly extends the trait. This script shows what happens when inverse is calculated:

In AbstractT2:
AbstractT2: value = 0, inverse = Infinity
In c2b:
c2b.value = 10, inverse = Infinity

As you might expect, the inverse is calculated too early. Note that a divide by zero exception isn’t thrown; the compiler recognizes the value is infinite, but it hasn’t actually “tried” the division yet!

The behavior of this script is actually quite subtle. As an exercise, try selectively removing (or commenting out) the different println statements, one at a time. Observe what happens to the results. Sometimes inverse is initialized properly! (Hint: remove the println("In c2b:") statement. Then try putting it back, but after the val value = 10 line.)

What this experiment really shows is that side effects (i.e., from the println statements) can be unexpected and subtle, especially during initialization. It’s best to avoid them.

Scala provides two solutions to this problem: lazy values, which we discuss in Lazy Vals, and pre-initialized fields, which is demonstrated in the following refinement to the previous example:

// code-examples/AdvOOP/overrides/trait-pre-init-val-script.scala

trait AbstractT2 {
  println("In AbstractT2:")
  val value: Int
  val inverse = 1.0/value
  println("AbstractT2: value = "+value+", inverse = "+inverse)
}

val c2c = new {
  // Only initializations are allowed in pre-init. blocks.
  // println("In c2c:")
  val value = 10
} with AbstractT2

println("c2c.value = "+c2c.value+", inverse = "+c2c.inverse)

We instantiate an anonymous inner class, initializing the value field in the block, before the with AbstractT2 clause. This guarantees that value is initialized before the body of AbstractT2 is executed, as shown when you run the script:

In AbstractT2:
AbstractT2: value = 10, inverse = 0.1
c2c.value = 10, inverse = 0.1

Also, if you selectively remove any of the println statements, you get the same expected and now predictable results.

Now let’s consider the second workaround we described earlier, changing the declaration to var. This solution is more suitable if a good default value exists and you don’t want to require instances that use the trait to always set the value. In this case, change the val to a var, either a public var or a private var hidden behind reader and writer methods. Either way, we can simply reassign the value in a derived trait or class.

Returning to our VetoableClicks example, here is the modified VetoableClicks trait that uses a public var for maxAllowed:

// code-examples/AdvOOP/ui3/vetoable-clicks.scala

package ui3
import observer._

trait VetoableClicks extends Clickable {
  var maxAllowed = 1       // default
  private var count = 0
  abstract override def click() = {
    count += 1
    if (count <= maxAllowed)
      super.click()
  }
}

Here is a new specs object, ButtonClickableObserverVetoableSpec2, that demonstrates changing the value of maxAllowed:

// code-examples/AdvOOP/ui3/button-clickable-observer-vetoable2-spec.scala
package ui3

import org.specs._
import observer._
import ui.ButtonCountObserver

object ButtonClickableObserverVetoableSpec2 extends Specification {
  "A Button Observer with Vetoable Clicks" should {
    "observe only the first 'maxAllowed' clicks" in {
      val observableButton =
        new Button("Okay") with ObservableClicks with VetoableClicks {
          maxAllowed = 2
      }
      observableButton.maxAllowed mustEqual 2
      val buttonClickCountObserver = new ButtonCountObserver
      observableButton.addObserver(buttonClickCountObserver)
      for (i <- 1 to 3) observableButton.click()
      buttonClickCountObserver.count mustEqual 2
    }
  }
}

No override var is required. We just assign a new value. Since the body of the trait is executed before the body of the class using it, reassigning the field value happens after the initial assignment in the trait’s body. However, as we saw before, that reassignment could happen too late if the field is used in the trait’s body in some calculation that will become invalid by a reassignment later! You can avoid this problem if you make the field private and define a public writer method that redoes any dependent calculations.

Another disadvantage of using a var declaration is that maxAllowed was not intended to be writable. As we will see in Chapter 8, read-only values have important benefits. We would prefer for maxAllowed to be read-only, at least after the construction process completes.

We can see that the simple act of changing the val to a var causes potential problems for the maintainer of VetoableClicks. Control over that field is now lost. The maintainer must carefully consider whether or not the value will change and if a change will invalidate the state of the instance. This issue is especially pernicious in multithreaded systems (see The Problems of Shared, Synchronized State).

Tip

Avoid var fields when possible (in classes as well as traits). Consider public var fields especially risky.

Overriding Abstract and Concrete Fields in Classes

In contrast to traits, overriding a val declared in a class works as expected. Here is an example with both a val override and a var reassignment in a derived class:

// code-examples/AdvOOP/overrides/class-field-script.scala

class C1 {
  val name = "C1"
  var count = 0
}

class ClassWithC1 extends C1 {
  override val name = "ClassWithC1"
  count = 1
}

val c = new ClassWithC1()
println(c.name)
println(c.count)

The override keyword is required for the concrete val field name, but not for the var field count. This is because we are changing the initialization of a constant (val), which is a “special” operation.

If you run this script, the output is the following:

ClassWithC1
1

Both fields are overridden in the derived class, as expected. Here is the same example modified so that both the val and the var are abstract in the base class:

// code-examples/AdvOOP/overrides/class-abs-field-script.scala

abstract class AbstractC1 {
  val name: String
  var count: Int
}

class ClassWithAbstractC1 extends AbstractC1 {
  val name = "ClassWithAbstractC1"
  var count = 1
}

val c = new ClassWithAbstractC1()
println(c.name)
println(c.count)

The override keyword is not required for name in ClassWithAbstractC1, since the original declaration is abstract. The output of this script is the following:

ClassWithAbstractC1
1

It’s important to emphasize that name and count are abstract fields, not concrete fields with default values. A similar-looking declaration of name in a Java class, String name;, would declare a concrete field with the default value (null in this case). Java doesn’t support abstract fields or types (as we’ll discuss next), only methods.

Overriding Abstract Types

We introduced abstract type declarations in Abstract Types And Parameterized Types. Recall the BulkReader example from that section:

// code-examples/TypeLessDoMore/abstract-types-script.scala

import java.io._

abstract class BulkReader {
  type In
  val source: In
  def read: String
}

class StringBulkReader(val source: String) extends BulkReader {
  type In = String
  def read = source
}

class FileBulkReader(val source: File) extends BulkReader {
  type In = File
  def read = {
    val in = new BufferedInputStream(new FileInputStream(source))
    val numBytes = in.available()
    val bytes = new Array[Byte](numBytes)
    in.read(bytes, 0, numBytes)
    new String(bytes)
  }
}

println( new StringBulkReader("Hello Scala!").read )
println( new FileBulkReader(new File("abstract-types-script.scala")).read )

Abstract types are an alternative to parameterized types, which we’ll explore in Understanding Parameterized Types. Like parameterized types, they provide an abstraction mechanism at the type level.

The example shows how to declare an abstract type and how to define a concrete value in derived classes. BulkReader declares type In without initializing it. The concrete derived class StringBulkReader provides a concrete value using type In = String.

Unlike fields and methods, it is not possible to override a concrete type definition. However, the abstract declaration can constrain the allowed concrete type values. We’ll learn how in Chapter 12.

Finally, you probably noticed that this example also demonstrates defining an abstract field, using a constructor parameter, and an abstract method.

For another example, let’s revisit our Subject trait from Traits As Mixins. The definition of the Observer type is a structural type with a method named receiveUpdate. Observers must have this “structure.” Let’s generalize the implementation now, using an abstract type:

// code-examples/AdvOOP/observer/observer2.scala

package observer

trait AbstractSubject {
  type Observer

  private var observers = List[Observer]()
  def addObserver(observer:Observer) = observers ::= observer
  def notifyObservers = observers foreach (notify(_))

  def notify(observer: Observer): Unit
}

trait SubjectForReceiveUpdateObservers extends AbstractSubject {
  type Observer = { def receiveUpdate(subject: Any) }

  def notify(observer: Observer): Unit = observer.receiveUpdate(this)
}

trait SubjectForFunctionalObservers extends AbstractSubject {
  type Observer = (AbstractSubject) => Unit

  def notify(observer: Observer): Unit = observer(this)
}

Now, AbstractSubject declares type Observer as abstract (implicitly, because there is no definition). Since the original structural type is gone, we don’t know exactly how to notify an observer. So, we also added an abstract method notify, which a concrete class or trait will define as appropriate.

The SubjectForReceiveUpdateObservers derived trait defines Observer with the same structural type we used in the original example, and notify simply calls receiveUpdate, as before.

The SubjectForFunctionalObservers derived trait defines Observer to be a function taking an instance of AbstractSubject and returning Unit. All notify has to do is call the observer function, passing the subject as the sole argument. Note that this implementation is similar to the approach we used in our original button implementation, ButtonWithCallbacks, where the “callbacks” were user-supplied functions. (See Introducing Traits and a revisited version in Constructors in Scala.)

Here is a specification that exercises these two variations, observing button clicks as before:

// code-examples/AdvOOP/observer/button-observer2-spec.scala

package ui
import org.specs._
import observer._

object ButtonObserver2Spec extends Specification {
  "An Observer watching a SubjectForReceiveUpdateObservers button" should {
    "observe button clicks" in {
      val observableButton =
        new Button(name) with SubjectForReceiveUpdateObservers {
        override def click() = {
          super.click()
          notifyObservers
        }
      }
      val buttonObserver = new ButtonCountObserver
      observableButton.addObserver(buttonObserver)
      for (i <- 1 to 3) observableButton.click()
      buttonObserver.count mustEqual 3
    }
  }
  "An Observer watching a SubjectForFunctionalObservers button" should {
    "observe button clicks" in {
      val observableButton =
        new Button(name) with SubjectForFunctionalObservers {
        override def click() = {
          super.click()
          notifyObservers
        }
      }
      var count = 0
      observableButton.addObserver((button) => count += 1)
      for (i <- 1 to 3) observableButton.click()
      count mustEqual 3
    }
  }
}

First we exercise SubjectForReceiveUpdateObservers, which looks very similar to our earlier examples. Next we exercise SubjectForFunctionalObservers. In this case, we don’t need another “observer” instance at all. We just maintain a count variable and pass a function literal to addObserver to increment the count (and ignore the button).

The main virtue of SubjectForFunctionalObservers is its minimalism. It requires no special instances, no traits defining abstractions, etc. For many cases, it is an ideal approach.

AbstractSubject is more reusable than the original definition of Subject, because it imposes fewer constraints on potential observers.

Note

AbstractSubject illustrates that an abstraction with fewer concrete details is usually more reusable.

But wait, there’s more! We’ll revisit the use of abstract types and the Observer Pattern in Scalable Abstractions.

When Accessor Methods and Fields Are Indistinguishable: The Uniform Access Principle

Suppose a user of ButtonCountObserver from Traits As Mixins accesses the count member:

// code-examples/Traits/ui/button-count-observer-script.scala

val bco = new ui.ButtonCountObserver
val oldCount = bco.count
bco.count = 5
val newCount = bco.count
println(newCount + " == 5 and " + oldCount + " == 0?")

When the count field is read or written, as in this example, are methods called or is the field accessed directly? As originally declared in ButtonCountObserver, the field is accessed directly. However, the user doesn’t really care. In fact, the following two definitions are functionally equivalent, from the perspective of the user:

class ButtonCountObserver {
  var count = 0  // public field access (original definition)
  // ...
}
class ButtonCountObserver {
  private var cnt = 0  // private field
  def count = cnt      // reader method
  def count_=(newCount: Int) = cnt = newCount  // writer method
  // ...
}

This equivalence is an example of the Uniform Access Principle. Clients read and write field values as if they are publicly accessible, even though in some cases they are actually calling methods. The maintainer of ButtonCountObserver has the freedom to change the implementation without forcing users to make code changes.

The reader method in the second version does not have parentheses. Recall that consistency in the use of parentheses is required if a method definition omits parentheses. This is only possible if the method takes no arguments. For the Uniform Access Principle to work, we want to define field reader methods without parentheses. (Contrast that with Ruby, where method parentheses are always optional as long as the parse is unambiguous.)

The writer method has the format count_=(...). As a bit of syntactic sugar, the compiler allows invocations of methods with this format to be written in either of the following ways:

obj.field_=(newValue)
// or
obj.field = newValue

We named the private variable cnt in the alternative definition. Scala keeps field and method names in the same namespace, which means we can’t name the field count if a method is named count. Many languages, like Java, don’t have this restriction because they keep field and method names in separate namespaces. However, these languages can’t support the Uniform Access Principle as a result, unless they build in ad hoc support in their grammars or compilers.

Since member object definitions behave similar to fields from the caller’s perspective, they are also in the same namespace as methods and fields. Hence, the following class would not compile:

// code-examples/AdvOOP/overrides/member-namespace-wont-compile.scala
// WON'T COMPILE

class IllegalMemberNameUse {
  def member(i: Int) = 2 * i
  val member = 2         // ERROR
  object member {        // ERROR
    def apply() = 2
  }
}

There is one other benefit of this namespace “unification.” If a parent class declares a parameterless method, then a subclass can override that method with a val. If the parent’s method is concrete, then the override keyword is required:

// code-examples/AdvOOP/overrides/method-field-class-script.scala

class Parent {
  def name = "Parent"
}

class Child extends Parent {
  override val name = "Child"
}

println(new Child().name)   // => "Child"

If the parent’s method is abstract, then the override keyword is optional:

// code-examples/AdvOOP/overrides/abs-method-field-class-script.scala

abstract class AbstractParent {
  def name: String
}

class ConcreteChild extends AbstractParent {
  val name = "Child"
}

println(new ConcreteChild().name)   // => "Child"

This also works for traits. If the trait’s method is concrete, we have the following:

// code-examples/AdvOOP/overrides/method-field-trait-script.scala

trait NameTrait {
  def name = "NameTrait"
}

class ConcreteNameClass extends NameTrait {
  override val name = "ConcreteNameClass"
}

println(new ConcreteNameClass().name)   // => "ConcreteNameClass"

If the trait’s method is abstract, we have the following:

// code-examples/AdvOOP/overrides/abs-method-field-trait-script.scala

trait AbstractNameTrait {
  def name: String
}

class ConcreteNameClass extends AbstractNameTrait {
  val name = "ConcreteNameClass"
}

println(new ConcreteNameClass().name)   // => "ConcreteNameClass"

Why is this feature useful? It allows derived classes and traits to use a simple field access when that is sufficient, or a method call when more processing is required, such as lazy initialization. The same argument holds for the Uniform Access Principle, in general.

Overriding a def with a val in a subclass can also be handy when interoperating with Java code. Turn a getter into a val by placing it in the constructor. You’ll see this in action in the following example, in which our Scala class Person implements a hypothetical PersonInterface from some legacy Java code:

class Person(val getName: String) extends PersonInterface

If you only have a few accessors in the Java code you’re integrating with, this technique makes quick work of them.

What about overriding a parameterless method with a var, or overriding a val or var with a method? These are not permitted because they can’t match the behaviors of the things they are overriding.

If you attempt to use a var to override a parameterless method, you get an error that the writer method, override name_=, is not overriding anything. This would also be inconsistent with a philosophical goal of functional programming, that a method that takes no parameters should always return the same result. To do otherwise would require side effects in the implementation, which functional programming tries to avoid, for reasons we will examine in Chapter 8. Because a var is changeable, the no-parameter “method” defined in the parent type would no longer return the same result consistently.

If you could override a val with a method, there would be no way for Scala to guarantee that the method would always return the same value, consistent with val semantics. That issue doesn’t exist with a var, of course, but you would have to override the var with two methods, a reader and a writer. The Scala compiler doesn’t support that substitution.

Companion Objects

Recall that fields and methods defined in objects serve the role that class “static” fields and methods serve in languages like Java. When object-based fields and methods are closely associated with a particular class, they are normally defined in a companion object.

We mentioned companion objects briefly in Chapter 1, and we discussed the Pair example from the Scala library in Chapter 2. Let’s fill in the remaining details now.

First, recall that if a class (or a type referring to a class) and an object are declared in the same file, in the same package, and with the same name, they are called a companion class (or companion type) and a companion object, respectively.

There is no namespace collision when the name is reused in this way, because Scala stores the class name in the type namespace, while it stores the object name in the term namespace (see [ScalaSpec2009]).

The two most interesting methods frequently defined in a companion object are apply and unapply.

Apply

Scala provides some syntactic sugar in the form of the apply method. When an instance of a class is followed by parentheses with a list of zero or more parameters, the compiler invokes the apply method for that instance. This is true for an object with a defined apply method (such as a companion object), as well as an instance of a class that defines an apply method.

In the case of an object, apply is conventionally used as a factory method, returning a new instance. This is what Pair.apply does in the Scala library. Here is Pair from the standard library:

type Pair[+A, +B] = Tuple2[A, B]
object Pair {
  def apply[A, B](x: A, y: B) = Tuple2(x, y)
  def unapply[A, B](x: Tuple2[A, B]): Option[Tuple2[A, B]] = Some(x)
}

So, you can create a new Pair as follows:

val p = Pair(1, "one")

It looks like we are somehow creating a Pair instance without a new. Rather than calling a Pair constructor directly, we are actually calling Pair.apply (i.e., the companion object Pair), which then calls Tuple2.apply on the Tuple2 companion object!

Tip

If there are several alternative constructors for a class and it also has a companion object, consider defining fewer constructors on the class and defining several overloaded apply methods on the companion object to handle the variations.

However, apply is not limited to instantiating the companion class. It could instead return an instance of a subclass of the companion class. Here is an example where we define a companion object Widget that uses regular expressions to parse a string representing a Widget subclass. When a match occurs, the subclass is instantiated and the new instance is returned:

// code-examples/AdvOOP/objects/widget.scala

package objects

abstract class Widget {
  def draw(): Unit
  override def toString() = "(widget)"
}

object Widget {
  val ButtonExtractorRE = """(button: label=([^,]+),s+(Widget))""".r
  val TextFieldExtractorRE = """(textfield: text=([^,]+),s+(Widget))""".r

  def apply(specification: String): Option[Widget] = specification match {
    case ButtonExtractorRE(label)   => new Some(new Button(label))
    case TextFieldExtractorRE(text) => new Some(new TextField(text))
    case _ => None
  }
}

Widget.apply receives a string “specification” that defines which class to instantiate. The string might come from a configuration file with widgets to create at startup, for example. The string format is the same format used by toString(). Regular expressions are defined for each type. (Parser combinators are an alternative. They are discussed in External DSLs with Parser Combinators.)

The match expression applies each regular expression to the string. A case expression like:

case ButtonExtractorRE(label) => new Some(new Button(label))

means that the string is matched against the ButtonExtractorRE regular expression. If successful, it extracts the substring in the first capture group in the regular expression and assigns it to the variable label. Finally, a new Button with this label is created, wrapped in a Some. We’ll learn how this extraction process works in the next section, Unapply.

A similar case handles TextField creation. (TextField is not shown. See the online code examples.) Finally, if apply can’t match the string, it returns None.

Here is a specs object that exercises Widget.apply:

// code-examples/AdvOOP/objects/widget-apply-spec.scala

package objects
import org.specs._

object WidgetApplySpec extends Specification {
  "Widget.apply with a valid widget specification string" should {
    "return a widget instance with the correct fields set" in {
      Widget("(button: label=click me, (Widget))") match {
        case Some(w) => w match {
          case b:Button => b.label mustEqual "click me"
          case x => fail(x.toString())
        }
        case None => fail("None returned.")
      }
      Widget("(textfield: text=This is text, (Widget))") match {
        case Some(w) => w match {
          case tf:TextField => tf.text mustEqual "This is text"
          case x => fail(x.toString())
        }
        case None => fail("None returned.")
      }
    }
  }
  "Widget.apply with an invalid specification string" should {
    "return None" in {
      Widget("(button: , (Widget)") mustEqual None
    }
  }
}

The first match statement implicitly invokes Widget.apply with the string "(button: label=click me, (Widget))". If a button wrapped in a Some is not returned with the label "click me", this test will fail. Next, a similar test for a TextField widget is done. The final test uses an invalid string and confirms that None is returned.

A drawback of this particular implementation is that we have hardcoded a dependency on each derived class of Widget in Widget itself, which breaks the Open-Closed Principle (see [Meyer1997] and [Martin2003]). A better implementation would use a factory design pattern from [GOF1995]. Nevertheless, the example illustrates how an apply method can be used as a real factory.

There is no requirement for apply in an object to be used as a factory. Neither is there any restriction on the argument list or what apply returns. However, because it is so common to use apply in an object as a factory, use caution when using apply for other purposes, as it could confuse users. However, there are good counterexamples, such as the use of apply in Domain-Specific Languages (see Chapter 11).

The factory convention is less commonly used for apply defined in classes. For example, in the Scala standard library, Array.apply(i: int) returns the element at index i in the array. Many of the other collections use apply in a similar way. So, users can write code like the following:

val a = Array(1,2,3,4)
println(a(2))  // => 3

Finally, as a reminder, although apply is handled specially by the compiler, it is otherwise no different from any other method. You can overload it, you can invoke it directly, etc.

Unapply

The name unapply suggests that it does the “opposite” operation of apply. Indeed, it is used to extract the constituent parts of an instance. Pattern matching uses this feature extensively. Hence, unapply is often defined in companion objects and is used to extract the field values from instances of the corresponding companion types. For this reason, unapply methods are called extractors.

Here is an expanded button.scala with a Button object that defines an unapply extractor method:

// code-examples/AdvOOP/objects/button.scala

package objects
import ui3.Clickable

class Button(val label: String) extends Widget with Clickable {

  def click() = {
    // Logic to give the appearance of clicking a button...
  }

  def draw() = {
    // Logic to draw the button on the display, web page, etc.
  }

  override def toString() = "(button: label="+label+", "+super.toString()+")"
}

object Button {
  def unapply(button: Button) = Some(button.label)
}

Button.unapply takes a single Button argument and returns a Some wrapping the label value. This demonstrates the protocol for unapply methods. They return a Some wrapping the extracted fields. (We’ll see how to handle more than one field in a moment.)

Here is a specs object that exercises Button.unapply:

// code-examples/AdvOOP/objects/button-unapply-spec.scala

package objects
import org.specs._

object ButtonUnapplySpec extends Specification {
  "Button.unapply" should {
    "match a Button object" in {
      val b = new Button("click me")
      b match {
        case Button(label) =>
        case _ => fail()
      }
    }
    "match a RadioButton object" in {
      val b = new RadioButton(false, "click me")
      b match {
        case Button(label) =>
        case _ => fail()
      }
    }
    "not match a non-Button object" in {
      val tf = new TextField("hello world!")
      tf match {
        case Button(label) => fail()
        case _ =>
      }
    }
    "extract the Button's label" in {
      val b = new Button("click me")
      b match {
        case Button(label) => label mustEqual "click me"
        case _ => fail()
      }
    }
    "extract the RadioButton's label" in {
      val rb = new RadioButton(false, "click me, too")
      rb match {
        case Button(label) => label mustEqual "click me, too"
        case _ => fail()
      }
    }
  }
}

The first three examples (in clauses) confirm that Button.unapply is only called for actual Button instances or instances of derived classes, like RadioButton.

Since unapply takes a Button argument (in this case), the Scala runtime type checks the instance being matched. It then looks for a companion object with an unapply method and invokes that method, passing the instance. The default case clause case _ is invoked for the instances that don’t type check as compatible. The pattern matching process is fully type-safe.

The remaining examples (in clauses) confirm that the correct values for the label are extracted. The Scala runtime automatically extracts the item in the Some.

What about extracting multiple fields? For a fixed set of known fields, a Some wrapping a Tuple is returned, as shown in this updated version of RadioButton:

// code-examples/AdvOOP/objects/radio-button.scala

package objects

/**
 * Button with two states, on or off, like an old-style,
 * channel-selection botton on a radio.
 */
class RadioButton(val on: Boolean, label: String) extends Button(label)

object RadioButton {
  def unapply(button: RadioButton) = Some((button.on, button.label))
                 // equivalent to: = Some(Pair(button.on, button.label))
}

A Some wrapping a Pair(button.on, button.label) is returned. As we discuss in The Predef Object, Pair is a type defined to be equal to Tuple2. Here is the corresponding specs object that tests it:

// code-examples/AdvOOP/objects/radio-button-unapply-spec.scala

package objects
import org.specs._

object RadioButtonUnapplySpec extends Specification {
  "RadioButton.unapply" should {
    "should match a RadioButton object" in {
      val b = new RadioButton(true, "click me")
      b match {
        case RadioButton(on, label) =>
        case _ => fail()
      }
    }
    "not match a Button (parent class) object" in {
      val b = new Button("click me")
      b match {
        case RadioButton(on, label) => fail()
        case _ =>
      }
    }
    "not match a non-RadioButton object" in {
      val tf = new TextField("hello world!")
      tf match {
        case RadioButton(on, label) => fail()
        case _ =>
      }
    }
    "extract the RadioButton's on/off state and label" in {
      val b = new RadioButton(true, "click me")
      b match {
        case RadioButton(on, label) => {
          label mustEqual "click me"
          on    mustEqual true
        }
        case _ => fail()
      }
    }
  }
}

Apply and UnapplySeq for Collections

What if you want to build a collection from a variable argument list passed to apply? What if you want to extract the first few elements from a collection and you don’t care about the rest of it?

In this case, you define apply and unapplySeq (“unapply sequence”) methods. Here are those methods from Scala’s own List class:

def apply[A](xs: A*): List[A] = xs.toList

def unapplySeq[A](x: List[A]): Some[List[A]] = Some(x)

The [A] type parameterization on these methods allows the List object, which is not parameterized, to construct a new List[A]. (See Understanding Parameterized Types for more details.) Most of the time, the type parameter will be inferred based on the context.

The parameter list xs: A* is a variable argument list. Callers of apply can pass as many A instances as they want, including none. Internally, variable argument lists are stored in an Array[A], which inherits the toList method from Iterable that we used here.

Tip

This is a handy idiom for API writers. Accepting variable arguments to a function can be convenient for users, and converting the arguments to a List is often ideal for internal management.

Here is an example script that uses List.apply implicitly:

// code-examples/AdvOOP/objects/list-apply-example-script.scala

val list1 = List()
val list2 = List(1, 2.2, "three", 'four)
val list3 = List("1", "2.2", "three", "four")
println("1: "+list1)
println("2: "+list2)
println("3: "+list3)

The 'four is a symbol, essentially an interned string. Symbols are more commonly used in Ruby, for example, where the same symbol would be written as :four. Symbols are useful for representing identities consistently.

This script yields the following output:

1: List()
2: List(1, 2.2, three, 'four)
3: List(1, 2.2, three, four)

The unapplySeq method is trivial; it returns the input list wrapped in a Some. However, this is sufficient for pattern matching, as shown in this example:

// code-examples/AdvOOP/objects/list-unapply-example-script.scala

val list = List(1, 2.2, "three", 'four)
list match {
  case List(x, y, _*) => println("x = "+x+", y = "+y)
  case _ => throw new Exception("No match! "+list)
}

The List(x, y, _*) syntax means we will only match on a list with at least two elements, and the first two elements will be assigned to x and y. We don’t care about the rest of the list. The _* matches zero or more remaining elements.

The output is the following:

x = 1, y = 2.2

We’ll have much more to say about List and pattern matching in Lists in Functional Programming.

Companion Objects and Java Static Methods

There is one more thing to know about companion objects. Whenever you define a main method to use as the entry point for an application, Scala requires you to put it in an object. However, at the time of this writing, main methods cannot be defined in a companion object. Because of implementation details in the generated code, the JVM won’t find the main method. This issue may be resolved in a future release. For now, you must define any main method in a singleton object (i.e., a “non-companion” object; see [ScalaTips]). Consider the following example of a simple Person class and companion object that attempts to define main:

// code-examples/AdvOOP/objects/person.scala

package objects

class Person(val name: String, val age: Int) {
  override def toString = "name: " + name + ", age: " + age
}

object Person {
  def apply(name: String, age: Int) = new Person(name, age)
  def unapply(person: Person) = Some((person.name, person.age))

  def main(args: Array[String]) = {
    // Test the constructor...
    val person = new Person("Buck Trends", 18)
    assert(person.name == "Buck Trends")
    assert(person.age  == 21)
  }
}

object PersonTest {
  def main(args: Array[String]) = Person.main(args)
}

This code compiles fine, but if you attempt to invoke Person.main, using scala -cp ... objects.Person, you get the following error:

java.lang.NoSuchMethodException: objects.Person.main([Ljava.lang.String;)

The objects/Person.class file exists. If you decompile it with javap -classpath ... objects.Person (refer to The scalap, javap, and jad Command-Line Tools), you can see that it doesn’t contain a main method. If you decompile objects/Person$.class, the file for the companion object’s byte code, it has a main method, but notice that it isn’t declared static. So, attempting to invoke scala -cp ... objects.Person$ also fails to find the “static” main:

java.lang.NoSuchMethodException: objects.Person$.main is not static

The separate singleton object PersonTest defined in this example has to be used. Decompiling it with javap -classpath ... objects.PersonTest shows that it has a static main method. If you invoke it using scala -cp ... objects.PersonTest, the PersonTest.main method is invoked, which in turn invokes Person.main. You get an assertion error from the second call to assert, which is intentional:

java.lang.AssertionError: assertion failed
    at scala.Predef$.assert(Predef.scala:87)
    at objects.Person$.test(person.scala:15)
    at objects.PersonTest$.main(person.scala:20)
    at objects.PersonTest.main(person.scala)
    ...

In fact, this is a general issue with methods defined in companion objects that need to be visible to Java code as static methods. They aren’t static in the byte code. You have to put these methods in singleton objects instead. Consider the following Java class that attempts to create a user with Person.apply:

// code-examples/AdvOOP/objects/PersonUserWontCompile.java
// WON'T COMPILE

package objects;

public class PersonUserWontCompile {
  public static void main(String[] args) {
    Person buck = Person.apply("Buck Trends", 100);  // ERROR
    System.out.println(buck);
  }
}

If we compile it (after compiling Person.scala), we get the following error:

$ javac -classpath ... objects/PersonUserWontCompile.java
objects/PersonUserWontCompile.java:5: cannot find symbol
symbol  : method apply(java.lang.String,int)
location: class objects.Person
        Person buck = Person.apply("Buck Trends", 100);
                            ^
1 error

However, we can use the following singleton object:

// code-examples/AdvOOP/objects/person-factory.scala

package objects

object PersonFactory {
  def make(name: String, age: Int) = new Person(name, age)
}

Now the following Java class will compile:

// code-examples/AdvOOP/objects/PersonUser.java

package objects;

public class PersonUser {
  public static void main(String[] args) {
    // The following line won't compile.
    // Person buck = Person.apply("Buck Trends", 100);
    Person buck = PersonFactory.make("Buck Trends", 100);
    System.out.println(buck);
  }
}

Warning

Do not define main or any other method in a companion object that needs to be visible to Java code as a static method. Define it in a singleton object, instead.

If you have no other choice but to call a method in a companion object from Java, you can explicitly create an instance of the object with new, since the object is a “regular” Java class in the byte code, and call the method on the instance.

Case Classes

In Matching on Case Classes, we briefly introduced you to case classes. Case classes have several useful features, but also some drawbacks.

Let’s rewrite the Shape example we used in A Taste of Concurrency to use case classes. Here is the original implementation:

// code-examples/IntroducingScala/shapes.scala

package shapes {
  class Point(val x: Double, val y: Double) {
    override def toString() = "Point(" + x + "," + y + ")"
  }

  abstract class Shape() {
    def draw(): Unit
  }

  class Circle(val center: Point, val radius: Double) extends Shape {
    def draw() = println("Circle.draw: " + this)
    override def toString() = "Circle(" + center + "," + radius + ")"
  }

  class Rectangle(val lowerLeft: Point, val height: Double, val width: Double)
        extends Shape {
    def draw() = println("Rectangle.draw: " + this)
    override def toString() =
      "Rectangle(" + lowerLeft + "," + height + "," + width + ")"
  }

  class Triangle(val point1: Point, val point2: Point, val point3: Point)
        extends Shape() {
    def draw() = println("Triangle.draw: " + this)
    override def toString() =
      "Triangle(" + point1 + "," + point2 + "," + point3 + ")"
  }
}

Here is the example rewritten using the case keyword:

// code-examples/AdvOOP/shapes/shapes-case.scala

package shapes {
  case class Point(x: Double, y: Double)

  abstract class Shape() {
    def draw(): Unit
  }

  case class Circle(center: Point, radius: Double) extends Shape() {
    def draw() = println("Circle.draw: " + this)
  }

  case class Rectangle(lowerLeft: Point, height: Double, width: Double)
      extends Shape() {
    def draw() = println("Rectangle.draw: " + this)
  }

  case class Triangle(point1: Point, point2: Point, point3: Point)
      extends Shape() {
    def draw() = println("Triangle.draw: " + this)
  }
}

Adding the case keyword causes the compiler to add a number of useful features automatically. The keyword suggests an association with case expressions in pattern matching. Indeed, they are particularly well suited for that application, as we will see.

First, the compiler automatically converts the constructor arguments into immutable fields (vals). The val keyword is optional. If you want mutable fields, use the var keyword. So, our constructor argument lists are now shorter.

Second, the compiler automatically implements equals, hashCode, and toString methods to the class, which use the fields specified as constructor arguments. So, we no longer need our own toString methods. In fact, the generated toString methods produce the same outputs as the ones we implemented ourselves. Also, the body of Point is gone because there are no methods that we need to define!

The following script uses these methods that are now in the shapes:

// code-examples/AdvOOP/shapes/shapes-usage-example1-script.scala

import shapes._

val shapesList = List(
  Circle(Point(0.0, 0.0), 1.0),
  Circle(Point(5.0, 2.0), 3.0),
  Rectangle(Point(0.0, 0.0), 2, 5),
  Rectangle(Point(-2.0, -1.0), 4, 3),
  Triangle(Point(0.0, 0.0), Point(1.0, 0.0), Point(0.0, 1.0)))

val shape1 = shapesList.head  // grab the first one.
println("shape1: "+shape1+". hash = "+shape1.hashCode)
for (shape2 <- shapesList) {
  println("shape2: "+shape2+". 1 == 2 ? "+(shape1 == shape2))
}

This script outputs the following:

shape1: Circle(Point(0.0,0.0),1.0). hash = 2061963534
shape2: Circle(Point(0.0,0.0),1.0). 1 == 2 ? true
shape2: Circle(Point(5.0,2.0),3.0). 1 == 2 ? false
shape2: Rectangle(Point(0.0,0.0),2.0,5.0). 1 == 2 ? false
shape2: Rectangle(Point(-2.0,-1.0),4.0,3.0). 1 == 2 ? false
shape2: Triangle(Point(0.0,0.0),Point(1.0,0.0),Point(0.0,1.0)). 1 == 2 ? false

As we’ll see in Equality of Objects, the == method actually invokes the equals method.

Even outside of case expressions, automatic generation of these three methods is very convenient for simple, “structural” classes, i.e., classes that contain relatively simple fields and behaviors.

Third, when the case keyword is used, the compiler automatically creates a companion object with an apply factory method that takes the same arguments as the primary constructor. The previous example used the appropriate apply methods to create the Points, the different Shapes, and also the List itself. That’s why we don’t need new; we’re actually calling apply(x,y) in the Point companion object, for example.

Note

You can have secondary constructors in case classes, but there will be no overloaded apply method generated that has the same argument list. You’ll have to use new to create instances with those constructors.

The companion object also gets an unapply extractor method, which extracts all the fields of an instance in an elegant fashion. The following script demonstrates the extractors in pattern matching case statements:

// code-examples/AdvOOP/shapes/shapes-usage-example2-script.scala

import shapes._

val shapesList = List(
  Circle(Point(0.0, 0.0), 1.0),
  Circle(Point(5.0, 2.0), 3.0),
  Rectangle(Point(0.0, 0.0), 2, 5),
  Rectangle(Point(-2.0, -1.0), 4, 3),
  Triangle(Point(0.0, 0.0), Point(1.0, 0.0), Point(0.0, 1.0)))

def matchOn(shape: Shape) = shape match {
  case Circle(center, radius) =>
    println("Circle: center = "+center+", radius = "+radius)
  case Rectangle(ll, h, w) =>
    println("Rectangle: lower-left = "+ll+", height = "+h+", width = "+w)
  case Triangle(p1, p2, p3) =>
    println("Triangle: point1 = "+p1+", point2 = "+p2+", point3 = "+p3)
  case _ =>
    println("Unknown shape!"+shape)
}

shapesList.foreach { shape => matchOn(shape) }

This script outputs the following:

Circle: center = Point(0.0,0.0), radius = 1.0
Circle: center = Point(5.0,2.0), radius = 3.0
Rectangle: lower-left = Point(0.0,0.0), height = 2.0, width = 5.0
Rectangle: lower-left = Point(-2.0,-1.0), height = 4.0, width = 3.0
Triangle: point1 = Point(0.0,0.0), point2 = Point(1.0,0.0), point3 = Point(0.0,1.0)

Syntactic Sugar for Binary Operations

By the way, remember in Matching on Sequences when we discussed matching on lists? We wrote this case expression:

def processList(l: List[Any]): Unit = l match {
  case head :: tail => ...
  ...
}

It turns out that the following expressions are identical:

case head :: tail => ...
  case ::(head, tail) => ...

We are using the companion object for the case class named ::, which is used for non-empty lists. When used in case expressions, the compiler supports this special infix operator notation for invocations of unapply.

It works not only for unapply methods with two arguments, but also with one or more arguments. We could rewrite our matchOn method this way:

def matchOn(shape: Shape) = shape match {
  case center Circle radius => ...
  case ll Rectangle (h, w) => ...
  case p1 Triangle (p2, p3) => ...
  case _ => ...
}

For an unapply that takes one argument, you would have to insert an empty set of parentheses to avoid a parsing ambiguity:

case arg Foo () => ...

From the point of view of clarity, this syntax is elegant for some cases when there are two arguments. For lists, head :: tail matches the expressions for building up lists, so there is a beautiful symmetry when the extraction process uses the same syntax. However, the merits of this syntax are less clear for other examples, especially when there are N != 2 arguments.

The copy Method in Scala Version 2.8

In Scala version 2.8, another instance method is automatically generated, called copy. This method is useful when you want to make a new instance of a case class that is identical to another instance with a few fields changed. Consider the following example script:

// code-examples/AdvOOP/shapes/shapes-usage-example3-v28-script.scala
// Scala version 2.8 only.

import shapes._

val circle1 = Circle(Point(0.0, 0.0), 2.0)
val circle2 = circle1 copy (radius = 4.0)

println(circle1)
println(circle2)

The second circle is created by copying the first and specifying a new radius. The copy method implementation that is generated by the compiler exploits the new named and default parameters in Scala version 2.8, which we discussed in Method Default and Named Arguments (Scala Version 2.8). The generated implementation of Circle.copy looks roughly like the following:

case class Circle(center: Point, radius: Double) extends Shape() {
  ...
  def copy(center: Point = this.center, radius: Double = this.radius) =
    new Circle(center, radius)
}

So, default values are provided for all the arguments to the method (only two in this case). When using the copy method, the user specifies by name only the fields that are changing. The values for the rest of the fields are used without having to reference them explicitly.

Case Class Inheritance

Did you notice that the new Shapes code in Case Classes did not put the case keyword on the abstract Shape class? This is allowed by the compiler, but there are reasons for not having one case class inherit another. First, it can complicate field initialization. Suppose we make Shape a case class. Suppose we want to add a string field to all shapes representing an id that the user wants to set. It makes sense to define this field in Shape. Let’s make these two changes to Shape:

abstract case class Shape(id: String) {
  def draw(): Unit
}

Now the derived shapes need to pass the id to the Shape constructor. For example, Circle would become the following:

case class Circle(id: String, center: Point, radius: Double) extends Shape(id){
  def draw(): Unit
}

However, if you compile this code, you’ll get errors like the following:

... error: error overriding value id in class Shape of type String;
 value id needs `override' modifier
  case class Circle(id: String, center: Point, radius: Double) extends Shape(id){
                    ^

Remember that both definitions of id, the one in Shape and the one in Circle, are considered val field definitions! The error message tells us the answer; use the override keyword, as we discussed in Overriding Members of Classes and Traits. So, the complete set of required modifications are as follows:

// code-examples/AdvOOP/shapes/shapes-case-id.scala

package shapesid {
  case class Point(x: Double, y: Double)

  abstract case class Shape(id: String) {
    def draw(): Unit
  }

  case class Circle(override val id: String, center: Point, radius: Double)
        extends Shape(id) {
    def draw() = println("Circle.draw: " + this)
  }

  case class Rectangle(override val id: String, lowerLeft: Point,
        height: Double, width: Double) extends Shape(id) {
    def draw() = println("Rectangle.draw: " + this)
  }

  case class Triangle(override val id: String, point1: Point,
        point2: Point, point3: Point) extends Shape(id) {
    def draw() = println("Triangle.draw: " + this)
  }
}

Note that we also have to add the val keywords. This works, but it is somewhat ugly.

A more ominous problem involves the generated equals methods. Under inheritance, the equals methods don’t obey all the standard rules for robust object equality. We’ll discuss those rules in Equality of Objects. For now, consider the following example:

// code-examples/AdvOOP/shapes/shapes-case-equals-ambiguity-script.scala

import shapesid._

case class FancyCircle(name: String, override val id: String,
    override val center: Point, override val radius: Double)
      extends Circle(id, center, radius) {
  override def draw() = println("FancyCircle.draw: " + this)
}

val fc = FancyCircle("me", "circle", Point(0.0,0.0), 10.0)
val c  = Circle("circle", Point(0.0,0.0), 10.0)
format("FancyCircle == Circle? %b
", (fc == c))
format("Circle == FancyCircle? %b
", (c  == fc))

If you run this script, you get the following output:

FancyCircle == Circle? false
Circle == FancyCircle? true

So, Circle.equals evaluates to true when given a FancyCircle with the same values for the Circle fields. The reverse case isn’t true. While you might argue that, as far as Circle is concerned, they really are equal, most people would argue that this is a risky, “relaxed” interpretation of equality. It’s true that a future version of Scala could generate equals methods for case classes that do exact type-equality checking.

So, the conveniences provided by case classes sometimes lead to problems. It is best to avoid inheritance of one case class by another. Note that it’s fine for a case class to inherit from a non-case class or trait. It’s also fine for a non-case class or trait to inherit from a case class.

Because of these issues, it is possible that case class inheritance will be deprecated and removed in future versions of Scala.

Warning

Avoid inheriting a case class from another case class.

Equality of Objects

Implementing a reliable equality test for instances is difficult to do correctly. Effective Java ([Bloch2008]) and the Scaladoc page for AnyRef.equals describe the requirements for a good equality test. A very good description of the techniques for writing correct equals and hashCode methods can be found in [Odersky2009], which uses Java syntax, but is adapted from Chapter 28 of Programming in Scala ([Odersky2008]). Consult these references when you need to implement your own equals and hashCode methods. Recall that these methods are created automatically for case classes.

Here we focus on the different equality methods available in Scala and their meanings. There are some slight inconsistencies between the Scala specification (see [ScalaSpec2009]) and the Scaladoc pages for the equality-related methods for Any and AnyRef, but the general behavior is clear.

Caution

Some of the equality methods have the same names as equality methods in other languages, but the semantics are sometimes different!

The equals Method

The equals method tests for value equality. That is, obj1 equals obj2 is true if both obj1 and obj2 have the same value. They do not need to refer to the same instance.

Hence, equals behaves like the equals method in Java and the eql? method in Ruby.

The == and != Methods

While == is an operator in many languages, it is a method in Scala, defined as final in Any. It tests for value equality, like equals. That is, obj1 == obj2 is true if both obj1 and obj2 have the same value. In fact, == delegates to equals. Here is part of the Scaladoc entry for Any.==:

o == arg0 is the same as o.equals(arg0).

Here is the corresponding part of the Scaladoc entry for AnyRef.==:

o == arg0 is the same as if (o eq null) arg0 eq null else o.equals(arg0).

As you would expect, != is the negation, i.e., it is equivalent to !(obj1 == obj2).

Since == and != are declared final in Any, you can’t override them, but you don’t need to, since they delegate to equals.

Note

In Java, C++, and C#, the == operator tests for reference, not value equality. In contrast, Ruby’s == operator tests for value equality. Whatever language you’re used to, make sure to remember that in Scala, == is testing for value equality.

The ne and eq Methods

The eq method tests for reference equality. That is, obj1 eq obj2 is true if both obj1 and obj2 point to the same location in memory. These methods are only defined for AnyRef.

Hence, eq behaves like the == operator in Java, C++, and C#, but not == in Ruby.

The ne method is the negation of eq, i.e., it is equivalent to !(obj1 eq obj2).

Array Equality and the sameElements Method

Comparing the contents of two Arrays doesn’t have an obvious result in Scala:

scala> Array(1, 2) == Array(1, 2)
res0: Boolean = false

That’s a surprise! Thankfully, there’s a simple solution in the form of the sameElements method:

scala> Array(1, 2).sameElements(Array(1, 2))
res1: Boolean = true

Much better. Remember to use sameElements when you want to test if two Arrays contain the same elements.

While this may seem like an inconsistency, encouraging an explicit test of the equality of two mutable data structures is a conservative approach on the part of the language designers. In the long run, it should save you from unexpected results in your conditionals.

Recap and What’s Next

We explored the fine points of overriding members in derived classes. We learned about object equality, case classes, and companion classes and objects.

In the next chapter, we’ll learn about the Scala type hierarchy—in particular, the Predef object that includes many useful definitions. We’ll also learn about Scala’s alternative to Java’s static class members and the linearization rules for method lookup.

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

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