ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 02.01.2026

Просмотров: 416

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Exercises 65

Exercises

1.Improve the Counter class in Section 5.1, “Simple Classes and Parameterless Methods,” on page 51 so that it doesn’t turn negative at Int.MaxValue.

2.Write a class BankAccount with methods deposit and withdraw, and a read-only property balance.

3.Write a class Time with read-only properties hours and minutes and a method before(other: Time): Boolean that checks whether this time comes before the other. A Time object should be constructed as new Time(hrs, min), where hrs is in military time format (between 0 and 23).

4.Reimplement the Time class from the preceding exercise so that the internal representation is the number of minutes since midnight (between 0 and 24 × 60 – 1). Do not change the public interface. That is, client code should be unaffected by your change.

5.Make a class Student with read-write JavaBeans properties name (of type String) and id (of type Long). What methods are generated? (Use javap to check.) Can you call the JavaBeans getters and setters in Scala? Should you?

6.In the Person class of Section 5.1, “Simple Classes and Parameterless Methods,” on page 51, provide a primary constructor that turns negative ages to 0.

7.Write a class Person with a primary constructor that accepts a string containing a first name, a space, and a last name, such as new Person("Fred Smith"). Supply read-only properties firstName and lastName. Should the primary constructor parameter be a var, a val, or a plain parameter? Why?

8.Make a class Car with read-only properties for manufacturer, model name, and model year, and a read-write property for the license plate. Supply four constructors. All require the manufacturer and model name. Optionally, model year and license plate can also be specified in the constructor. If not, the model year is set to -1 and the license plate to the empty string. Which constructor are you choosing as the primary constructor? Why?

9.Reimplement the class of the preceding exercise in Java, C#, or C++ (your choice). How much shorter is the Scala class?

10.Consider the class

class Employee(val name: String, var salary: Double) { def this() { this("John Q. Public", 0.0) }

}

Rewrite it to use explicit fields and a default primary constructor. Which form do you prefer? Why?

Objects

Topics in This Chapter A1

6.1Singletons — page 67

6.2Companion Objects — page 68

6.3Objects Extending a Class or Trait — page 69

6.4The apply Method — page 69

6.5Application Objects — page 70

6.6Enumerations — page 71

Exercises — page 73


Chapter 6

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.

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.

To avoid the main method, use an object that extends the App trait.

You can implement enumerations by extending the Enumeration object.

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 }

}

67


68

Chapter 6 Objects

 

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 69). There is just one exception: You cannot provide constructor parameters.

You use an object in Scala whenever you would have used a singleton object in Java or C++:

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.

6.2 Companion Objects

In Java or C++, you often have a class with both instance methods and static methods. In Scala, you 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.

6.4

 

The apply Method

69

 

NOTE: The companion object of a class is accessible, but it is not in scope. For example, the Account class has to call Account.newUniqueNumber() and not just newUniqueNumber() to invoke the method of the companion object.

TIP: In the REPL, you must define the class and the object together in paste mode. Type

:paste

Then type or paste both the class and object definitions, and type Ctrl+D.

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.4The apply Method

It is common to have objects with an 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.


70

Chapter 6 Objects

 

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

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

Why doesn’t one just use a constructor? Not having the new keyword is handy for nested expressions, such as

Array(Array(1, 7), Array(2, 9))

CAUTION: It is easy to confuse Array(100) and new Array(100). The first expression calls apply(100), yielding an Array[Int] with a single element, the integer 100.The second expression invokes the constructor this(100).The result is an Array[Nothing] with 100 null elements.

Here is an example of defining an apply method:

class Account private (val id: Int, initialBalance: Double) { private var balance = initialBalance

...

}

object Account { // The companion object def apply(initialBalance: Double) =

new Account(newUniqueNumber(), initialBalance)

...

}

Now you can construct an account as

val acct = Account(1000.0)

6.5 Application Objects

Each Scala program must start with an object’s main method of type Array[String] => Unit:

object Hello {

def main(args: Array[String]) { println("Hello, World!")

}

}

Instead of providing a main method for your application, you can extend the App trait and place the program code into the constructor body:

6.6

 

Enumerations

71

 

object Hello extends App { println("Hello, World!")

}

If you need the command-line arguments, you can get them from the args property:

object Hello extends App { if (args.length > 0)

println("Hello, " + args(0)) else

println("Hello, World!")

}

If you invoke the application with the scala.time option set, then the elapsed time is displayed when the program exits.

$ scalac Hello.scala

$ scala -Dscala.time Hello Fred Hello, Fred

[total 4ms]

All this involves a bit of magic. The App trait extends another trait, DelayedInit, that gets special handling from the compiler. All initialization code of a class with that trait is moved into a delayedInit method. The main of the App trait method captures the command-line arguments, calls the delayedInit method, and optionally prints the elapsed time.

NOTE: Older versions of Scala had an Application trait for the same purpose. That trait carried out the program’s action in the static initializer, which is not optimized by the just-in-time compiler. Use the App trait instead.

6.6 Enumerations

Unlike Java or C++, Scala does not have enumerated types. However, the standard library provides an Enumeration helper class that you can use to produce enumerations.

Define an object that extends the Enumeration class and initialize each value in your enumeration with a call to the Value method. For example,

object TrafficLightColor extends Enumeration { val Red, Yellow, Green = Value

}

Here we define three fields, Red, Yellow, and Green, and initialize each of them with a call to Value. This is a shortcut for


72

Chapter 6 Objects

 

val Red = Value val Yellow = Value val Green = Value

Each call to the Value method returns a new instance of an inner class, also called

Value.

Alternatively, you can pass IDs, names, or both to the Value method:

val Red = Value(0, "Stop")

val Yellow = Value(10) // Name "Yellow" val Green = Value("Go") // ID 11

If not specified, the ID is one more than the previously assigned one, starting with zero. The default name is the field name.

You can now refer to the enumeration values as TrafficLightColor.Red, TrafficLightColor.Yellow, and so on. If that gets too tedious, use a statement

import TrafficLightColor._

(See Chapter 7 for more information on importing members of a class or object.)

Remember that the type of the enumeration is TrafficLightColor.Value and not TrafficLightColor—that’s the type of the object holding the values. Some people recommend that you add a type alias

object TrafficLightColor extends Enumeration { type TrafficLightColor = Value

val Red, Yellow, Green = Value

}

Now the type of the enumeration is TrafficLightColor.TrafficLightColor, which is only an improvement if you use an import statement. For example,

import TrafficLightColor._

def doWhat(color: TrafficLightColor) = { if (color == Red) "stop"

else if (color == Yellow) "hurry up" else "go"

}

The ID of an enumeration value is returned by the id method, and its name by the toString method.

The call TrafficLightColor.values yields a set of all values:

for (c <- TrafficLightColor.values) println(c.id + ": " + c)

Finally, you can look up an enumeration value by its ID or name. Both of the following yield the object TrafficLightColor.Red:

Exercises 73

TrafficLightColor(0) // Calls Enumeration.apply

TrafficLightColor.withName("Red")

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 the 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).