Use Sets and Maps



scala> val demoSet = Set("HHH", "BBB")
demoSet: scala.collection.immutable.Set[java.lang.String] = Set(HHH, BBB)

scala> val set2 = Set("Hello", 'a')
set2: scala.collection.immutable.Set[Any] = Set(Hello, a)

scala> import scala.collection.mutable.Set
import scala.collection.mutable.Set
scala> val movieSet = Set("Hitch", "Poltergeist")
movieSet: scala.collection.mutable.Set[java.lang.String] = Set(Poltergeist, Hitch)

Scala中創建集的方法與創建列表和陣列的類似:通過調用Set伴生對象的名為apply的工廠方法。代碼3.5中,對 scala.collection.immutable.Set的伴生對象調用了apply方法,返回了一個缺省的,不可變Set的實例。Scala編譯 器推斷jetSet的類型為不可變Set[String]。

Generic: <> -> [ ]

數組表示為泛型化的 Array 類的實例,這正是 Scala 使用方括號(「[]」)而非尖括號(「<>」)來指明參數化類型的原因。


Because Scala aims to help you take advantage of both functional and imperative styles, its collections libraries make a point to differentiate between mutable and immutable collection classes. For example, Arrays are always mutable, whereas Lists are always immutable. When it comes to Sets and Maps, Scala also provides mutable and immutable alternatives, but in a different way. For Sets and Maps, Scala models mutability in the class hierarchy.

For example, the Scala API contains a base trait for Sets, where a trait is similar to a Java interface. (You'll find out more about traits in Step 12.) Scala then provides two subtraits, one for mutable Sets, and another for immutable Sets. As you can see in Figure 2, these three traits all share the same simple name, Set. Their fully qualified names differ, however, because they each reside in a different package. Concrete Set classes in the Scala API, such as the HashSet classes shown in Figure 2, extend either the mutable or immutable Set trait. (Although in Java you implement interfaces, in Scala you “extend” traits.) Thus, if you want to use a HashSet, you can choose between mutable and immutable varieties depending upon your needs.

Figure 2. Class hierarchy for Scala Sets.

To try out Scala Sets, type the following code into a file named jetset.scala:

import scala.collection.mutable.HashSet

val jetSet = new HashSet[String]
jetSet += "Lear"
jetSet += ("Boeing", "Airbus")

The first line of jetSet.scala imports the mutable HashSet. As with Java, the import allows you to use the simple name of the class, HashSet, in this source file. After a blank line, the third line initializes jetSet with a new HashSet that will contain only Strings. Note that just as with Lists and Arrays, when you create a Set, you need to parameterize it with a type (in this case, String), since every object in a Set must share the same type. The subsequent two lines add three objects to the mutable Set via the += method. As with most other symbols you've seen that look like operators in Scala, += is actually a method defined on class HashSet. Had you wanted to, instead of writing jetSet += "Lear", you could have written jetSet.+=("Lear"). Because the += method takes a variable number of arguments, you can pass one or more objects at a time to it. For example, jetSet += "Lear" adds one String to the HashSet, but jetSet += ("Boeing", "Airbus") adds two Strings to the set. Finally, the last line prints out whether or not the Set contains a particular String. (As you'd expect, it prints false.)

Another useful collection class in Scala is Maps. As with Sets, Scala provides mutable and immutable versions of Map, using a class hierarchy. As you can see in Figure 3, the class hierarchy for Maps looks a lot like the one for Sets. There's a base Map trait in package scala.collection, and two subtrait Maps: a mutable Map in scala.collection.mutable and an immutable one in scala.collection.immutable.

Figure 3. Class hierarchy for Scala Maps.

Implementations of Map, such as the HashMaps shown in the class hierarchy in Figure 3, implement either the mutable or immutable trait. To see a Map in action, type the following code into a file named treasure.scala.

// In treasure.scala

import scala.collection.mutable.HashMap

val treasureMap = new HashMap[Int, String]
treasureMap += 1 -> "Go to island."
treasureMap += 2 -> "Find big X on ground."
treasureMap += 3 -> "Dig."

On the first line of treasure.scala, you import the mutable form of HashMap. After a blank line, you define a val named treasureMap and initialize it with a new mutable HashMap whose keys will be Ints and values Strings. On the next three lines you add key/value pairs to the HashMap using the -> method. As illustrated in previous examples, the Scala compiler transforms an binary operation expression like 1 -> "Go to island." into 1.->("Go to island."). Thus, when you say 1 -> "Go to island.", you are actually calling a method named -> on an Int with the value 1, and passing in a String with the value "Go to island." This -> method, which you can invoke on any object in a Scala program3, returns a two-element tuple containing the key and value. You then pass this tuple to the += method of the HashMap object to which treasureMap refers. Finally, the last line prints the value that corresponds to the key 2 in the treasureMap. If you run this code, it will print:

Find big X on ground.

Because maps are such a useful programming construct, Scala provides a factory method for Maps that is similar in spirit to the factory method shown in Step 9 that allows you to create Lists without using the new keyword. To try out this more concise way of constructing maps, type the following code into a file called numerals.scala:

// In numerals.scala
val romanNumeral = Map(1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V")

In numerals.scala you take advantage of the fact that the the immutable Map trait is automatically imported into any Scala source file. Thus when you say Map in the first line of code, the Scala interpreter knows you mean scala.collection.immutable.Map. In this line, you call a factory method on the immutable Map's companion object5, passing in five key/value tuples as parameters. This factory method returns an instance of the immutable HashMap containing the passed key/value pairs. The name of the factory method is actually apply, but as mentioned in Step 8, if you say Map(...) it will be transformed by the compiler to Map.apply(...). If you run the numerals.scala script, it will print IV.