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

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

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

Добавлен: 02.01.2026

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

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

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

Section 17.4

Chapter 17 · Collections

394

scala> import scala.collection.immutable.TreeSet import scala.collection.immutable.TreeSet

scala> val treeSet = TreeSet(colors)

<console>:15: error: could not find implicit value for parameter ord: Ordering[List[java.lang.String]]

val treeSet = TreeSet(colors)

ˆ

Instead, you’ll need to create an empty TreeSet[String] and add to it the elements of the list with the TreeSet’s ++ operator:

scala> val treeSet = TreeSet[String]() ++ colors treeSet: scala.collection.immutable.TreeSet[String]

= TreeSet(blue, green, red, yellow)

Converting to array or list

If you need to initialize a list or array with another collection, on the other hand, it is quite straightforward. As you’ve seen previously, to initialize a new list with another collection, simply invoke toList on that collection:

scala> treeSet.toList

res50: List[String] = List(blue, green, red, yellow)

Or, if you need an array, invoke toArray:

scala> treeSet.toArray

res51: Array[String] = Array(blue, green, red, yellow)

Note that although the original colors list was not sorted, the elements in the list produced by invoking toList on the TreeSet are in alphabetical order. When you invoke toList or toArray on a collection, the order of the elements in the resulting list or array will be the same as the order of elements produced by an iterator obtained by invoking elements on that collection. Because a TreeSet[String]’s iterator will produce strings in alphabetical order, those strings will appear in alphabetical order in the list resulting from invoking toList on that TreeSet.

Keep in mind, however, that conversion to lists or arrays usually requires copying all of the elements of the collection, and thus may be slow for large collections. Sometimes you need to do it, though, due to an existing API.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 17.4

Chapter 17 · Collections

395

Further, many collections only have a few elements anyway, in which case there is only a small speed penalty.

Converting between mutable and immutable sets and maps

Another situation that arises occasionally is the need to convert a mutable set or map to an immutable one, or vice versa. To accomplish this, you can use the technique shown on the previous page to initialize a TreeSet with the elements of a list. Create a collection of the new type using the empty method and then add the new elements using either ++ or ++=, whichever is appropriate for the target collection type. Here’s how you’d convert the immutable TreeSet from the previous example to a mutable set, and back again to an immutable one:

scala> import scala.collection.mutable import scala.collection.mutable

scala> treeSet

res52: scala.collection.immutable.TreeSet[String] = TreeSet(blue, green, red, yellow)

scala> val mutaSet = mutable.Set.empty ++= treeSet mutaSet: scala.collection.mutable.Set[String] =

Set(yellow, blue, red, green)

scala> val immutaSet = Set.empty ++ mutaSet immutaSet: scala.collection.immutable.Set[String] =

Set(yellow, blue, red, green)

You can use the same technique to convert between mutable and immutable maps:

scala> val muta = mutable.Map("i" -> 1, "ii" -> 2)

muta: scala.collection.mutable.Map[java.lang.String,Int] = Map(ii -> 2, i -> 1)

scala> val immu = Map.empty ++ muta

immu: scala.collection.immutable.Map[java.lang.String,Int] = Map(ii -> 2, i -> 1)

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index



Section 17.5

Chapter 17 · Collections

396

17.5 Tuples

As described in Step 9 in Chapter 3, a tuple combines a fixed number of items together so that they can be passed around as a whole. Unlike an array or list, a tuple can hold objects with different types. Here is an example of a tuple holding an integer, a string, and the console:

(1, "hello", Console)

Tuples save you the tedium of defining simplistic data-heavy classes. Even though defining a class is already easy, it does require a certain minimum effort, which sometimes serves no purpose. Tuples save you the effort of choosing a name for the class, choosing a scope to define the class in, and choosing names for the members of the class. If your class simply holds an integer and a string, there is no clarity added by defining a class named

AnIntegerAndAString.

Because tuples can combine objects of different types, tuples do not inherit from Traversable. If you find yourself wanting to group exactly one integer and exactly one string, then you want a tuple, not a List or Array.

A common application of tuples is returning multiple values from a method. For example, here is a method that finds the longest word in a collection and also returns its index:

def longestWord(words: Array[String]) = { var word = words(0)

var idx = 0

for (i <- 1 until words.length)

if (words(i).length > word.length) { word = words(i)

idx = i

}

(word, idx)

}

Here is an example use of the method:

scala> val longest =

longestWord("The quick brown fox".split(" ")) longest: (String, Int) = (quick,1)

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 17.5

Chapter 17 · Collections

397

The longestWord function here computes two items: word, the longest word in the array, and idx, the index of that word. To keep things simple, the function assumes there is at least one word in the list, and it breaks ties by choosing the word that comes earlier in the list. Once the function has chosen which word and index to return, it returns both of them together using the tuple syntax (word, idx).

To access elements of a tuple, you can use method _1 to access the first element, _2 to access the second, and so on:

scala> longest._1 res53: String = quick

scala> longest._2 res54: Int = 1

Additionally, you can assign each element of the tuple to its own variable,5 like this:

scala> val (word, idx) = longest word: String = quick

idx: Int = 1

scala> word

res55: String = quick

By the way, if you leave off the parentheses you get a different result:

scala> val word, idx = longest word: (String, Int) = (quick,1) idx: (String, Int) = (quick,1)

This syntax gives multiple definitions of the same expression. Each variable is initialized with its own evaluation of the expression on the right-hand side. That the expression evaluates to a tuple in this case does not matter. Both variables are initialized to the tuple in its entirety. See Chapter 18 for some examples where multiple definitions are convenient.

As a note of warning, tuples are almost too easy to use. Tuples are great when you combine data that has no meaning beyond “an A and a B.” However, whenever the combination has some meaning, or you want to add some

5This syntax is actually a special case of pattern matching, as described in detail in Section 15.7.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 17.6

Chapter 17 · Collections

398

methods to the combination, it is better to go ahead and create a class. For example, do not use a 3-tuple for the combination of a month, a day, and a year. Make a Date class. It makes your intentions explicit, which both clears up the code for human readers and gives the compiler and language opportunities to help you catch mistakes.

17.6 Conclusion

This chapter has given an overview of the Scala collections library and the most important classes and traits in it. With this foundation you should be able to work effectively with Scala collections, and know where to look in Scaladoc when you need more information. For more detailed information about Scala collections, look ahead to Chapter 24 and Chapter 25. For now, in the next chapter, we’ll turn our attention from the Scala library back to the language and discuss Scala’s support for mutable objects.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Chapter 18

Stateful Objects

In previous chapters, we put the spotlight on functional (immutable) objects. We did so because the idea of objects without any mutable state deserves to be better known. However, it is also perfectly possible to define objects with mutable state in Scala. Such stateful objects often come up naturally when you want to model objects in the real world that change over time.

This chapter explains what stateful objects are, and what Scala provides in terms of syntax to express them. The second part of this chapter introduces a larger case study on discrete event simulation, which involves stateful objects as well as building an internal domain specific language (DSL) for defining digital circuits to simulate.

18.1 What makes an object stateful?

You can observe the principal difference between a purely functional object and a stateful one even without looking at the object’s implementation. When you invoke a method or dereference a field on some purely functional object, you will always get the same result. For instance, given a list of characters:

val cs = List('a', 'b', 'c')

an application of cs.head will always return 'a'. This is the case even if there is an arbitrary number of operations on the list cs between the point where it is defined and the point where the access cs.head is made.

For a stateful object, on the other hand, the result of a method call or field access may depend on what operations were previously performed on the

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 18.1

Chapter 18 · Stateful Objects

400

object. A good example of a stateful object is a bank account. Listing 18.1 shows a simplified implementation of bank accounts:

class BankAccount {

private var bal: Int = 0

def balance: Int = bal

def deposit(amount: Int) { require(amount > 0)

bal += amount

}

def withdraw(amount: Int): Boolean = if (amount > bal) false

else {

bal -= amount true

}

}

Listing 18.1 · A mutable bank account class.

The BankAccount class defines a private variable, bal, and three public methods: balance returns the current balance; deposit adds a given amount to bal; and withdraw tries to subtract a given amount from bal while assuring that the remaining balance won’t be negative. The return value of withdraw is a Boolean indicating whether the requested funds were successfully withdrawn.

Even if you know nothing about the inner workings of the BankAccount class, you can still tell that BankAccounts are stateful objects:

scala> val account = new BankAccount account: BankAccount = BankAccount@bf5bb7

scala> account deposit 100

scala> account withdraw 80 res1: Boolean = true

scala> account withdraw 80 res2: Boolean = false

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index