Chapter 6. Objects and Enumerations

Topics in This Chapter A1

In this short chapter, you will learn when to use the object construct in Scala. Use it when you need a class with a single instance, or when you want to find a home for miscellaneous values or functions. This chapter also covers enumerated types.

The key points of this chapter are:

  • Use objects for singletons and utility methods.

  • A class can have a companion object with the same name.

  • Objects can extend classes or traits.

  • The apply method of an object is usually used for constructing new instances of the companion class.

  • If you don’t provide your own apply method in the companion object, an apply method is provided for all constructors of a class.

  • Instead of a @main-annotated method, you can provide a main(Array[String]): Unit method in an object as your program’s starting point.

  • The enum construct defines an enumeration. Enumeration objects can have state and methods.

6.1 Singletons

Scala has no static methods or fields. Instead, you use the object construct. An object defines a single instance of a class with the features that you want. For example,

object Accounts :
  private var lastNumber = 0
  def newUniqueNumber() =
    lastNumber += 1
    lastNumber

When you need a new unique account number in your application, call Accounts.newUniqueNumber().

The constructor of an object is executed when the object is first used. In our example, the Accounts constructor is executed with the first call to Accounts.newUniqueNumber(). If an object is never used, its constructor is not executed.

An object can have essentially all the features of a class—it can even extend other classes or traits (see Section 6.3, “Objects Extending a Class or Trait,” on page 79). There is just one exception: You cannot provide constructor parameters.

Use an object in Scala whenever you would have used a singleton object in another programming language:

  • As a home for utility functions or constants

  • When a single immutable instance can be shared efficiently

  • When a single instance is required to coordinate some service (the singleton design pattern)

Images Note

Many people view the singleton design pattern with disdain. Scala gives you the tools for both good and bad design, and it is up to you to use them wisely.

6.2 Companion Objects

In Java, JavaScript, or C++, you often have a class with both instance methods and static methods. In Scala, you can achieve this by having a class and a “companion” object of the same name. For example,

class Account :
  val id = Account.newUniqueNumber()
  private var balance = 0.0
  def deposit(amount: Double) =
    balance += amount
  ...

object Account : // The companion object
  private var lastNumber = 0
  private def newUniqueNumber() =
    lastNumber += 1
    lastNumber

The class and its companion object can access each other’s private features. They must be located in the same source file.

Note that the companion object’s features are not in the scope of the class. For example, the Account class has to use Account.newUniqueNumber() and not just newUniqueNumber() to invoke the method of the companion object.

Images Tip

To define a companion object in the REPL, write both the class and the object in a text editor, and then paste both of them together into the REPL. If you define them separately, you will get an error message.

6.3 Objects Extending a Class or Trait

An object can extend a class and/or one or more traits. The result is an object of a class that extends the given class and/or traits, and in addition has all of the features specified in the object definition.

One useful application is to specify default objects that can be shared. For example, consider a class for undoable actions in a program.

abstract class UndoableAction(val description: String) :
  def undo(): Unit
  def redo(): Unit

A useful default is the “do nothing” action. Of course, we only need one of them.

object DoNothingAction extends UndoableAction("Do nothing") :
  override def undo() = ()
  override def redo() = ()

The DoNothingAction object can be shared across all places that need this default.

val actions = Map("open" -> DoNothingAction, "save" -> DoNothingAction)
  // Open and save not yet implemented

6.4 The apply Method

The apply method is called for expressions of the form

Object(arg1, ..., argN)

Typically, such an apply method returns an object of the companion class.

For example, the Array object defines apply methods that allow array creation with expressions such as

Array("Mary", "had", "a", "little", "lamb")

This call actually means:

Array.apply("Mary", "had", "a", "little", "lamb")

Whenever you define a Scala class, a companion object is automatically provided, with an apply method for every constructor. This is what enables construction without using the new operator.

For example, given the class

class Person(val name: String, val age: Int)

there is automatically a companion object Person and a method Person.apply so that you can call

val p = Person("Fred", 42)
val q = Person.apply("Wilma", 39)

These methods are called constructor proxy methods.

There is only one exception. If the class already has a companion object with at least one apply method, then no constructor proxy methods are provided.

You might want to declare your own apply method if you don’t want to invoke a fixed constructor. The most common reason is to produce instances of a subtype. For example, Map.apply produces maps of different classes:

val seasons = Map("Spring" -> 1, "Summer" -> 2, "Fall" -> 3, "Winter" -> 4)
seasons.getClass // Yields class scala.collection.immutable.Map$Map4
val directions =
  Map("Center" -> 0, "North" -> 1, "East" -> 2, "South" -> 3, "West" -> 4)
directions.getClass // Yields class scala.collection.immutable.HashMap

6.5 Application Objects

If an object declares a method with name main and type Array[String] => Unit, then you can invoke that method from the command line. Consider the classic example, a file Hello.scala with this contents:

object Hello :
  def main(args: Array[String]) =
    println(s"Hello, ${args.mkString(" ")}!")

Now you can compile and run the class. The command-line arguments are placed in the args parameter.

$ scalac Hello.scala
$ scala Hello cruel world
Hello, cruel world!

This mechanism is a little different than the one you saw in Chapter 2, where the program’s entry point was a function with the @main annotation. Consider a file Greeter.scala with this contents:

@main def hello(args: String*) =
  println(s"Hello, ${args.mkString(" ")}!")

The annotation produces an object named hello with a public static void main(Array[String]) method. That method parses the command-line arguments and calls the hello method, which is located in another class Hello$package$.

To invoke the program, run the hello class:

$ scalac Greeter.scala
$ scala hello cruel world
Hello, cruel world!

6.6 Enumerations

An object has a single instance. An enumerated type has a finite number of instances:

enum TrafficLightColor :
  case Red, Yellow, Green

Here we define a type TrafficLightColor with three instances, TrafficLightColor.Red, TrafficLightColor.Yellow, and TrafficLightColorGreen.

Every enum automatically has an ordinal method that yields an integer for each instance, numbering them in the order in which they were defined, starting at 0.

TrafficLightColor.Yellow.ordinal // Yields 1

The inverse is the fromOrdinal method of the companion object:

TrafficLightColor.fromOrdinal(1) // Yields TrafficLightColor.Yellow

The values method of the companion object yields an array of all enumerated values in the same order:

TrafficLightColor.values // Yields Array(Red, Yellow, Green)

The companion object has a valueOf method that obtains an instance by name:

TrafficLightColor.valueOf("Yellow")

This is the inverse of the toString method, which maps instances to their names:

TrafficLightColor.Yellow.toString // Yields "Yellow"

You can define your own methods:

enum TrafficLightColor :
  case Red, Yellow, Green
  def next = TrafficLightColor.fromOrdinal((ordinal + 2) % 3)

You can also add methods to the companion object:

object TrafficLightColor :
  def random() = TrafficLightColor.fromOrdinal(scala.util.Random.nextInt(3))

Instances can have state. Provide a constructor with the enumeration type, and then construct instances as follows:

enum TrafficLightColor(val description: String) :
  case Red extends TrafficLightColor("Stop")
  case Yellow extends TrafficLightColor("Hurry up")
  case Green extends TrafficLightColor("Go")

The extends syntax reflects the fact that the enumeration instances are objects extending the enumeration type—see Section 6.3, “Objects Extending a Class or Trait,” on page 79.

You can deprecate an instance:

enum TrafficLightColor :
  case Red, Yellow, Green
   @deprecated("""https://99percentinvisible.org/article/stop-at-red-go-on-grue-
how-language-turned-traffic-lights-bleen-in-japan/""") case Blue

If you need to make an enumerated type compatible with Java enumerations, extend the java.lang.Enum class, as follows:

enum TrafficLightColor extends Enum[TrafficLightColor] :
  case Red, Yellow, Green

Images Note

In Chapter 14, you will see “parameterized” enum that define class hierarchies, such as:

enum Amount :
  case Nothing
  case Dollar(value: Double)
  case Currency(value: Double, unit: String)

This is equivalent to an abstract class Amount that is extended by an object Nothing and classes Dollar and Currency.

The enum types covered in this chapter are a special case, with only objects.

Exercises

1. Write an object Conversions with methods inchesToCentimeters, gallonsToLiters, and milesToKilometers.

2. The preceding problem wasn’t very object-oriented. Provide a general superclass UnitConversion and define objects InchesToCentimeters, GallonsToLiters, and MilesToKilometers that extend it.

3. Define an Origin object that extends java.awt.Point. Why is this not actually a good idea? (Have a close look at the methods of the Point class.)

4. Define a Point class with a companion object so that you can construct Point instances as Point(3, 4), without using new.

5. Write a Scala application, using the App trait, that prints its command-line arguments in reverse order, separated by spaces. For example, scala Reverse Hello World should print World Hello.

6. Write an enumeration describing the four playing card suits so that the toString method returns Images or Images.

7. Implement a function that checks whether a card suit value from the preceding exercise is red.

8. Write an enumeration describing the eight corners of the RGB color cube. As IDs, use the color values (for example, 0xff0000 for Red).

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

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