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.
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)
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.
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 objectprivate 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.
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.
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
apply
MethodThe 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$Map4val directions =
Map("Center" -> 0, "North" -> 1, "East" -> 2, "South" -> 3, "West" -> 4)
directions.getClass //
Yields class scala.collection.immutable.HashMap
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!
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
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.
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 or .
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
).