Chap 0 前言

focus on:

  1. Scala 的语法十分简洁

  2. Scala 运行在虚拟机之上, 可以使用 java 的海量类库和工具

  3. Scala 拥抱函数式编程的同时,并没有废弃面向对象

  4. Scala 既有动态语言那样的灵活简洁,同时有保留了静态类型检查的安全与执行效率

  5. Scala 既能处理脚本化的临时任务,又能处理高并发场景下的分布式互联网大数据应用,可谓能缩能伸

Chap 1 基础

focus on:

  1. 使用 scala 解释器

  2. 用 var 和 val 定义变量

  3. 数字类型

  4. 使用操作符和函数

  5. 浏览 Scaladoc

1.1 Scala解释器

scala> 8 * 5 + 2
res1: Int = 42

scala> 0.5 * res1
res4: Double = 21.0

scala> "Hello, " + res1
res5: String = Hello, 42
{ String = java.lang.String }

scala 程序并不是一个解释器。
输入的内容被快速地编译成字节码,然后这段字节码交由 Java 虚拟机执行。
我们称之为 : REPL

scala> res5.to  // tab 补全
toByte   toChar   toDouble   toFloat   toInt   toLong   toShort   toString

scala> res5.toUpperCase
res6: String = HELLO, 42

1.2 声明值和变量

Scala 鼓励使用 val, 除非你真的需要改变它。声明值和变量不初始化会报错。

注: 你不需要给出值和变量的类型,scala会根据初始化的值推断出来。

scala> val answer = 8 * 5 + 2
answer: Int = 42

scala> answer * 0.5
res8: Double = 21.0

在必要的时候,你也可是指定类型

scala> val greeting: String = null
greeting: String = null
scala> val greeting: Any = "Hello"
greeting: Any = Hello

scala> val xmax, ymax = 100
xmax: Int = 100
ymax: Int = 100

1.3 常用类型

Byte、Char、Short、Int、Long、Float、Double。和 Boolean。

与 Java 不同的是,这些类型是 类。

scala> 1.toString()
res9: String = 1

scala> 1.to(10)
res10: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Scala不需要包装类型。在基本类型和包装类型之间转换是 Scala 编译器的工作。

Scala 用 java.lang.String 类来表示字符串。不过,它通过 StringOps 类给字符串追加了上百种操作。

举例 intersect : 

scala> "Hello".intersect("World")
res11: String = lo

在这个表达式中,java.lang.String 对象 "Hello" 被隐式地转换成一个 StringOps 对象,接着 StringOps 类的 intersect 方法被应用。

同样 Scala 还提供了 RichInt、RichDouble、RichChar 等 to 方法就是 RichInt 类中的方法。

还有 BigInt 和 BigDecimal类,用于任意大小(但有穷)的数字。这些类背后是 java.math.BigInteger 和 java.math.Bigdecimal,在scala中,他们用起来更方便,可以用常规操作符来操作它们。

scala> val x: BigInt = 1234567890
x: BigInt = 1234567890

scala> x * x * x
res13: scala.math.BigInt = 1881676371789154860897069000

1.4 算术/操作符重载

    + - * / % 位 & | ^ >> << 都是完成通常的工作。
    只是有一点特别,这些操作符实际上是方法。  
    a + b 其实是  a.+(b)      + 是方法名。

Scala 并不会傻乎乎的 对方法名中使用非字母或数字 这种做法 带有偏见

如 BigInt 类就定义了一个名为 /% 的方法,该方法返回一个对偶 (商、余数)
1.to(10) 也可以写成 1 to 10.

Scala 没有提供 ++, -- 这种操作符。

1.5 调用函数和方法

除了方法之外,scala 还提供函数。相比 Java,在 Scala 中使用数学函数 (比如 : min 或 pow) 更为简单 ---- 你不需要从某个类调用它的静态方法

scala> import scala.math._ 或 import math._ 说明: _ 通配符类似java *
import scala.math._

scala> sqrt(2) 或者 math.sqrt(2)
res15: Double = 1.4142135623730951

scala> pow(2, 4)
res16: Double = 16.0

scala> min(3, Pi)
res17: Double = 3.0

Scala 没有静态方法,

Scala有一个特性,叫做单例对象(singleton object)

通常一个类对应有一个 伴生对象 (companion object),其方法就跟 Java 中的静态方法一样。举例来说,BigInt 类的 BigInt 伴生对象有一个生成指定位数的随机素数的方法 probablePrime:

scala> BigInt.probablePrime(100, scala.util.Random)
res33: scala.math.BigInt = 882882747840768533709728498879

说明 : 这里的 Random 是一个单例随机数生成器对象,而该对象是在 scala.util 包中定义的。这里用单例对象比用类更好的为数不多的场景之一。在Java中,为每个随机数都构造出一个新的java.util.Random对象是一个常见的错误。

Scala 没有参数且不改变当前对象的方法不带圆括号。如 :

scala> "Hello".distinct
res34: String = Helo

1.6 apply 方法

在 Scala 中,我们通常都会使用类似函数调用的语法。

scala> "Hello"(4)
res35: Char = o

相当于 C++ s[i], Java 的 s.charAt(i)

举例来说 在 StringOps 类的文档中,你会发现这样一个方法
 def apple(n: Int): Char
 
 "Hello"(4) 相当于 "Hello".apply(4)

如果去看 BigInt 伴生对象的文档,就会看到让你将字符串或数字转换为 BigInt 对象的 apply 方法。

scala> BigInt("12345")
res36: scala.math.BigInt = 12345

scala> BigInt.apply("12345")
res37: scala.math.BigInt = 12345

这个语句产生一个新的 BigInt 对象,不需要使用 new。
使用伴生对象apply方法是 Scala中 构建对象的常用手法

scala> Array(1, 4, 9, 16)
res38: Array[Int] = Array(1, 4, 9, 16)

scala> Array.apply(1, 4, 9, 16)
res39: Array[Int] = Array(1, 4, 9, 16)

1.7 Scaladoc

Java 程序员使用 Javadoc 浏览 Java API。

Scaladoc www.scala-lang.org/api 在线浏览 Scaladoc

www.scala-lang.org/download#api

注意每个类名旁边的 O 和 C,它们分别链接到对应的类 (C) 或 伴生对象 (O).

  • 如果你想使用数值类型,记得看看 RichInt、RichDouble等。字符串看StringOps

  • 数学函数 scala.math 包中

  • BigInt 有一个方法叫做 unary_-. 这就是你定义前置的负操作符 -x 的方式

  • 标记为 implicit 的方法对应的是自动(隐式)转换。比如: BigInt 对象拥有在需要时自动被调用的由 int 和 long 转换为 BigInt 的方法。

  • 方法可以以函数作为参数。 如 def count(p: (Char) => Boolean) : Int
    调用类似方法时,你通常可以一种非常紧凑的表示法给出函数定义。

scala> var s = "HelLo"
s: String = HelLo

scala> s.count(_.isUpper)
res42: Int = 2
  • 最后,当你偶尔遇到类似 StringOps 类中这样的看上去几乎没法一下子理解的方法签名时. 例如下情况 :

    def patch [B >: Char, That](from: Int, patch: GenSeq[B], replaced: Int) (implicit bf: CanBuildFrom[String, B, That]): That
  • 别紧张,直接忽略即可,还有另一个版本的 patch, 看上去容易讲得通

    def patch(from: Int, that: GenSeq[Char], replace: Int): StringOps[A]
  • 如果你把 GenSeq[Char] 和 StringOps[A] 都当做 String 的话,这个方法从文档理解起来就简单多了。当然,在 REPL 中试用也很容易:

    scala> "Harry".patch(1, "ung", 2)
    res43: String = Hungry

Chap 2 控制结构和函数

focus on:

  1. Scala 中,几乎所有的构造出来语法结构都有值。(区别于Java语句没有值)

  2. if、块、 表达式 有值

  3. void 类型是 Unit

  4. 避免在函数定义中使用 return

  5. 注意别在函数式定义中漏掉了=。

  6. Scala 没有受检异常

2.1 条件表达式

scala> var x = -4
x: Int = -4

scala> val s = if (x > 0) 1 else -1
s: Int = -1

scala> if (x > 0) "positive" else -1
res0: Any = -1
{说明 : 类型不同,返回值为公共超类型 Any}

scala> if (x > 0) 1
res1: AnyVal = ()

scala> if (x > 0) 1 else ()
res2: AnyVal = ()
{说明 :  这两条语句相同。()=Unit, (java void)}

2.2 块表达式-赋值

scala> val k = {val dx = 2; val dy = 3; math.sqrt(dx * dx + dy * dy)}
k: Double = 3.605551275463989

scala> x = y = 1
<console>:9: error: type mismatch;
 found   : Unit
 required: Int
       x = y = 1
             ^
{注意 : 赋值语句的值是 Unit}

2.3 输入和输出

scala> print("Answer: ")
Answer:
scala> print(42)
42
scala> println("Answer : " + 42)
Answer : 42

scala> // C风格 的 printf
scala> printf("Hello, %s! You are %d years old. \n", "fern", 28)
Hello, fern! You are 28 years old.

从控制台读取 readLine, readInt, readDouble ...

scala> val name = readLine("Your name : ")
warning: there was one deprecation warning; re-run with -deprecation for details
Your name : name: String = Bean

scala> val age = readInt()
warning: there was one deprecation warning; re-run with -deprecation for details
age: Int = 25

readDouble、readByte、readShort、readLong、
readFloat、readBoolean、readChar。

2.4 循环

scala> var n = 3
n: Int = 3
scala> var r = 1
r: Int = 1

scala> :paste
// Entering paste mode (ctrl-D to finish)
while (n > 0) {
  r = r * n
  n -= 1
}
// Exiting paste mode, now interpreting.

scala> print(n)
0
scala> print(r)
6
scala> var n = 3
n: Int = 3

scala> 1 to n
res13: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)

scala> for (i <- 1 to n) {
     |   r = r*i
     | }
scala> print(r)
36

说明 :

  • RichInt 类 这个 to 方法,返回 Range(区间)

  • 在 for 循环的变量之前并没有 val 或 var 的指定。该变量的类型是集合的原色类型。循环变量的作用域一直持续到循环结束。

scala> val s = "Hello"
s: String = Hello

scala> var sum = 0
sum: Int = 0

scala> for (i <- 0 until s.length)
     | sum += s(i)

scala> sum
res7: Int = 500

scala> var sum = 0
sum: Int = 0

你可以直接遍历字符序列,不需要使用下标

scala> for (ch <- "Hello") sum += ch

scala> sum
res9: Int = 500

说明 : Scala 并没有直接提供 break 或 continue 语句来退出循环。

2.5 for循环、for推导式

可以以 变量 <- 表达式 的形式提供多个生成器,用分号将它们隔开。

scala> for (i <- 1 to 3; j <- 1 to 3) print ((10*i + j) + " ")
11 12 13 21 22 23 31 32 33

生成器可以带着守卫条件

scala> for (i <- 1 to 3; j <- 1 to 3 if i != j) print ((10*i + j) + " ")
12 13 21 23 31 32

注意 : if 之前没有 分号

===

你可以使用任意多的定义,引入可以在循环中使用的变量 :

scala> for (i <- 1 to 3; from = 4 - i; j <- from to 3) print ((10*i + j) + " ")
13 22 23 31 32 33

循环体以 yield 开始,则该循环会构造出一个集合,每次迭代生成集合中的一个值

scala> for (i <- 1 to 10) yield i % 3
res12: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 0, 1, 2, 0, 1, 2, 0, 1)

这叫做 for推导式。 for推导式 生成的集合 与 它的第一个生成器是类型兼容的

scala> for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar
res16: String = HIeflmlmop

scala> for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar
res17: scala.collection.immutable.IndexedSeq[Char] = Vector(H, e, l, l, o, I, f, m, m, p)

scala> 0 to 3
res20: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3)

2.7 函数

定义函数,给出: 名称、参数、函数体

scala> def abs(x: Double) = if (x >= 0) x else -x
abs: (x: Double)Double

scala> abs(-5)
res22: Double = 5.0

scala> def fac(n: Int) = {
     | var r = 1
     | for (i <- 1 to n) r = r * i
     | r
     | }
fac: (n: Int)Int

scala> fac(3)
res23: Int = 6

我们最好适应没有 return 的日子,很快你就可以使用大量匿名函数。return 相当于 函数版的 break。

对于递归函数,必须指定返回类型,Scala 不是 Haskell, Scala 猜测不出来

scala> def fact(n: Int): Int = if (n <= 0) 1 else n * fact(n-1)
fact: (n: Int)Int

scala> fact(4)
res24: Int = 24

2.8 默认参数和带名参数

scala> def decorate(str: String, left: String = "[", right: String = "]") = left + str + right
decorate: (str: String, left: String, right: String)String

scala> decorate("Hello")
res26: String = [Hello]

scala> decorate(left = "<<<", str = "Hello", right = ">>>")
res29: String = <<<Hello>>>

2.9 变长参数

scala> def sum(args : Int*) = {
     |   var result = 0
     |   for (arg <- args) result += arg
     |   result
     | }
sum: (args: Int*)Int

scala> val s = sum(1, 4, 9, 16, 25)
s: Int = 55

值的序列 调用是错误的。 告诉编译器 当做 参数序列 处理

scala> val s = sum(1 to 5)
<console>:8: error: type mismatch;
 found   : scala.collection.immutable.Range.Inclusive
 required: Int
       val s = sum(1 to 5)
                     ^

scala> val s = sum(1 to 5: _*)
s: Int = 15

递归这样解决

scala> def resurSum(args: Int*): Int = {
     |   if (args.length == 0) 0
     |   else args.head + resurSum(args.tail: _*)
     | }
resurSum: (args: Int*)Int

scala> resurSum(1, 2, 3, 4, 5)
res31: Int = 15

因为 序列的 head 是它的首个元素,tail 是 其他元素的序列。

2.10 过程

不返回值函数的特殊表示法。 不建议使用

2.11 懒值

当 val 被声明为 lazy 时,它的初始化将被推迟,直到我们首次对它取值。

lazy val words = scala.io.Source.fromFile("~/words").mkString

懒值对于开销较大的初始化语句而言十分有用。

可以把 懒值 当做是介于 val 和 def 的中间状态。

说明 : 懒值并不是没有额外开销。我们每次访问懒值,都会有一个方法被调用,而这个方法将会以线程安全的方式检查该值是否已被初始化。

2.12 异常

Scala 的异常工作机制 和 Java / C++ 类似。

如 :

throw new IllegalArgumentException("x should not be negative")

当前运算被中止,运行时系统查找可接受 IllegalArgumentException 的异常处理器

和 Java 一样,抛出的对象必须是 java.lang.Throwable 的子类。不过,与 Java 不同的是,Scala 没有 “受检” 异常 -- 你不需要声明说函数或方法可能会抛出某种异常。

throw 表达式有特殊的类型 Nothing。 这在 if/else 表达式中有用。如果一个分支的类型是 Nothing,那么 if/else 表达式的类型就是另一个分支的类型。举例来说, 考虑如下代码

scala> var x = -1
x: Int = -1

scala> if (x >= 0) {
     |   math.sqrt(x)
     | } else throw new IllegalArgumentException("x should not be negative")
java.lang.IllegalArgumentException: x should not be negative
  ... 37 elided

第一个分支类型是 Double,第二个分支类型是 Nothing。 因此,if/else 表达式的类型是 Double。

捕获异常的语法采用的是模式匹配的语法。

scala> try {
     |   process(new URL("http://horstmann.com/fred-tiny.gif"))
     | } catch {
     |   case _: MalformedURLException => println("Bad URL: " + url)
     |   case ex: IOException => ex.printStackTrace()
     | }

和 Java 一样,更通用的异常应该排在更具体的异常之后。

注意,如果你需要使用捕获的异常对象,可以使用 _ 来替代变量名

try / finally 语句让你可以释放资源。

try { ... } finally { ... }

try { ... } catch { ... } finally { ... }

Chap 3 数组操作

focus on:

  1. 若 长度固定 则使用 Array, 若长度可能有变化则使用 ArrayBuffer。

  2. 提供初始值时不要使用 new

  3. 用 () 来访问元素

  4. 用 for (elem <- arr) 来遍历元素

  5. 用 for (elem <- arr if ...)...yield... 原数组转型为新数组

  6. Scala数组 和 Java数组 可以互操作; 用 ArrayBuffer, 使用 scala.collection.JavaConversions 中的转换函数

3.1 定长数组

scala> val nums = new Array[Int](10)
nums: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

scala> val a = new Array[String](10)
a: Array[String] = Array(null, null, null, null, null, null, null, null, null, null)

已经提供初始值就不需要 new.

scala> val s = Array("Hello", "World")
s: Array[String] = Array(Hello, World)

scala> s(0) = "Goodbye"

scala> s
res7: Array[String] = Array(Goodbye, World)
scala> Array(2,3,5,7)
res8: Array[Int] = Array(2, 3, 5, 7)

Array(2,3,5,7) 在 JVM 中是 int[]

3.2 变长数组

Java ArrayList -- C++ vector -- Scala ArrayBuffer

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

scala> val b = ArrayBuffer[Int]()
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

scala> b += 1
res9: b.type = ArrayBuffer(1)

scala> b += (1, 2, 3, 5)
res11: b.type = ArrayBuffer(1, 1, 2, 3, 5)

scala> b ++= Array(8, 13, 21)
res12: b.type = ArrayBuffer(1, 1, 2, 3, 5, 8, 13, 21)

scala> b.trimEnd(5)

scala> b
res15: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 2)

在数组的尾端添加 或 删除元素是一个高效的操作。

在任意位置插入或移除元素是低效。(之后的元素都需要平移)

scala> b.insert(2, 6)

scala> b
res15: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 6, 2)

scala> b.insert(2, 7, 8, 9)

scala> b.remove(2)
res17: Int = 7

scala> b.remove(2, 3)

scala> b
res19: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 2)

scala> b.toArray
res20: Array[Int] = Array(1, 1, 2)

scala> b.toBuffer
res22: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 1, 2)
scala> val c = b.toBuffer
c: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 1, 2)

scala> c
res24: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 1, 2)

scala> val c = b.toArray
c: Array[Int] = Array(1, 1, 2)

scala> c
res25: Array[Int] = Array(1, 1, 2)

3.3 遍历数组和数组缓冲

for (i <- 0 until a.length)
  println(i + ": " + a(i))
  
0 until 10 高带 0.until(10)

0 until (a.length, 2)
  // Range(0, 2, 4, ...)
  
0 until (a.length).reverse

for (elem <- a)
  println(elem)

3.4 数组转换

scala> val a = Array(2, 3, 5, 7)
a: Array[Int] = Array(2, 3, 5, 7)

scala> val result = for (elem <- a) yield 2 * elem
result: Array[Int] = Array(4, 6, 10, 14)
  // 从 数组缓冲 出发,也会得到另一个数组缓冲

scala> for (elem <- a if elem % 2 == 0) yield 2 * elem
res26: Array[Int] = Array(4)

scala> a
res27: Array[Int] = Array(2, 3, 5, 7)

函数式编程

scala> a.filter(_ % 2 == 0).map(2 * _)
res28: Array[Int] = Array(4)

考虑如下示例 :
给定一个整数的数组缓冲,我们想要移除除第一个负数之外的所有负数。

1). 收集保留的下标

var first = true
val indexs = for (i <- 0 until a.length if first || a(i) >= 0) yield { if (a(i) < 0) first = false; i }

2). 元素移动到该去的位置

for (j <- 0 until indexs.length) a(j) = a(indexs(j))
a.trimEnd(a.length - indexs.length)

3.5 常用算法

cala> Array(1, 7, 2).sum
res0: Int = 10
  // ArrayBuffer the same

scala> Array(1, 7, 2).min
res1: Int = 1

scala> Array(1, 7, 2).max
res2: Int = 7

scala> Array(1, 7, 2.5).max
res4: Double = 7.0

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

scala> ArrayBuffer("Mary", "had", "little", "lamb").max
res9: String = little

sorted 方法将 Array or ArrayBuffer 排序并返回经过排序的 Array or ArrayBuffer

不改变原数组,产生新数组

scala> val b = ArrayBuffer(1, 7, 2, 9).sorted
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 7, 9)

scala> val b = ArrayBuffer(1, 7, 2, 9).sortWith(_>_)
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(9, 7, 2, 1)

改变原数组

scala> val a = Array(1, 7, 2, 9)
a: Array[Int] = Array(1, 7, 2, 9)

scala> scala.util.Sorting.quickSort(a)

scala> a
res11: Array[Int] = Array(1, 2, 7, 9)

对于 min, max 和 quickSort 方法,元素类型必须支持比较操作, 这包括了数字、字符串以及其他带有 Ordered 特质的类型。

显示 Array 或 ArrayBuffer 的内容,用 mkString

scala> a.mkString
res12: String = 1279

scala> a.mkString(",")
res13: String = 1,2,7,9

scala> a.mkString("<", ",", ">")
res14: String = <1,2,7,9>

scala> a.toString
res15: String = [I@6691eb1e

3.6 解读 Scaladoc

对 Array类 的操作方法列在 ArrayOps 相关条目下。从技术上讲,在数组上对用这些操作之前,数组都会被转换成 ArrayOps对象

3.7 多维数组

Double 的二维数组类型为 Array[Array[Double]]. 构造用 ofDim 方法。

scala> val matrix = Array.ofDim[Double](3, 4)
matrix: Array[Array[Double]] = Array(Array(0.0, 0.0, 0.0, 0.0), Array(0.0, 0.0, 0.0, 0.0), Array(0.0, 0.0, 0.0, 0.0))

scala> matrix(1)(2) = 42

scala> matrix
res19: Array[Array[Double]] = Array(Array(0.0, 0.0, 0.0, 0.0), Array(0.0, 0.0, 42.0, 0.0), Array(0.0, 0.0, 0.0, 0.0))

创建不规则数组

scala> val triangle = new Array[Array[Int]](10)
triangle: Array[Array[Int]] = Array(null, null, null, null, null, null, null, null, null, null)

scala> for (i <- 0 until triangle.length)
     |   triangle(i) = new Array[Int](i+1)

scala> triangle
res21: Array[Array[Int]] = Array(Array(0), Array(0, 0), Array(0, 0, 0), Array(0, 0, 0, 0), Array(0, 0, 0, 0, 0), Array(0, 0, 0, 0, 0, 0), Array(0, 0, 0, 0, 0, 0, 0), Array(0, 0, 0, 0, 0, 0, 0, 0), Array(0, 0, 0, 0, 0, 0, 0, 0, 0), Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0))

3.8 与 Java 的互操作

可以引入 scala.collection.JavaConversions 里的隐式转换方法。

java.lang.ProcessBuilder类 有一个以 List<String> 为参数的构造器

import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer

scala> val command = ArrayBuffer("ls", "-al", "/home/data0")
command: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(ls, -al, /home/data0)

// Scala 到 Java 的转换
scala> val pb = new ProcessBuilder(command)
pb: ProcessBuilder = java.lang.ProcessBuilder@467eb8eb
import scala.collection.mutable.Buffer
import scala.collection.JavaConversions.asScalaBuffer

scala> val cmd: Buffer[String] = pb.command() // Java到Scala转换
cmd: scala.collection.mutable.Buffer[String] = ArrayBuffer(ls, -al, /home/data0)

scala> command
res25: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(ls, -al, /home/data0)

Chap 4 映射和元组

focus on:

  1. Scala 有十分易用的语法来创建、查询、遍历映射

  2. 你需要从可变和不可变的映射中做出选择

  3. 默认你得到的是 哈希映射, 你也可以指明要 树形映射

  4. Scala映射 和 Java映射 之间来回切换

  5. 元组可以用来聚集值

4.1 构造映射

不可变的映射

scala> val scores = Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
scores: scala.collection.immutable.Map[String,Int] = Map(Alice -> 10, Bob -> 3, Cindy -> 8)

可变的映射

scala> val scores = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
scores: scala.collection.mutable.Map[String,Int] = Map(Bob -> 3, Alice -> 10, Cindy -> 8)

new 空映射

scala> val scores = new scala.collection.mutable.HashMap[String, Int]
scores: scala.collection.mutable.HashMap[String,Int] = Map()

Scala 中,映射是对偶的集合。

scala> "Alice"->10
res26: (String, Int) = (Alice,10)

scala> val scores = Map(("Alice", 10), ("Bob", 3))
scores: scala.collection.immutable.Map[String,Int] = Map(Alice -> 10, Bob -> 3)

4.2 获取映射中的值

scala> val bobsScore = scores("Bob")
bobsScore: Int = 3

scala> val bobsScore = if (scores.contains("Bob")) scores("Bob") else 0
bobsScore: Int = 3

scala> val bobsScore = if (scores.contains("Bob")) scores("Bobo") else 0
java.util.NoSuchElementException: key not found: Bobo
  at scala.collection.MapLike$class.default(MapLike.scala:228)
  at scala.collection.AbstractMap.default(Map.scala:59)
  at scala.collection.MapLike$class.apply(MapLike.scala:141)
  at scala.collection.AbstractMap.apply(Map.scala:59)
  ... 33 elided

scala> val bobsScore = if (scores.contains("Bobo")) scores("Bob") else 0
bobsScore: Int = 0

scala> val bobsScore = scores.getOrElse("Bob", 0)
bobsScore: Int = 3  // 快捷写法

4.3 更新映射中的值

scala> val scores = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
scores: scala.collection.mutable.Map[String,Int] = Map(Bob -> 3, Alice -> 10, Cindy -> 8)

scala> scores("Bob")=10

scala> val bobsScore = scores.getOrElse("Bob", 0)
bobsScore: Int = 10

scala> scores("Kevin")=100

scala> scores
res30: scala.collection.mutable.Map[String,Int] = Map(Bob -> 10, Kevin -> 100, Alice -> 10, Cindy -> 8)

scala> scores += ("Bob"->90, "Fred"->7)
res31: scores.type = Map(Bob -> 90, Kevin -> 100, Fred -> 7, Alice -> 10, Cindy -> 8)

scala> scores
res32: scala.collection.mutable.Map[String,Int] = Map(Bob -> 90, Kevin -> 100, Fred -> 7, Alice -> 10, Cindy -> 8)

scala> scores -= "Alice"
res33: scores.type = Map(Bob -> 90, Kevin -> 100, Fred -> 7, Cindy -> 8)

scala> scores
res34: scala.collection.mutable.Map[String,Int] = Map(Bob -> 90, Kevin -> 100, Fred -> 7, Cindy -> 8)

scala> scores -= "Alice00"
res36: scores.type = Map(Bob -> 90, Kevin -> 100, Fred -> 7, Cindy -> 8)

4.4 迭代映射

scala> scores.keySet
res40: scala.collection.Set[String] = Set(Bob, Kevin, Fred, Cindy)

scala> scores.values
res46: Iterable[Int] = HashMap(90, 100, 7, 8)

scala> for ((k, v) <- scores) yield { print (k, v) }
(Bob,90)(Kevin,100)(Fred,7)(Cindy,8)res42: scala.collection.mutable.Iterable[Unit] = ArrayBuffer((), (), (), ())

scala> for ((k, v) <- scores) yield (v, k)
res45: scala.collection.mutable.Map[Int,String] = Map(8 -> Cindy, 100 -> Kevin, 7 -> Fred, 90 -> Bob)

4.5 已排序映射

操作映射的时候,你需要选定一个实现 : 哈希表 or 平衡树. default hashtable

得到一个不可变的树形映射,而不是哈希映射

scala> val scores = scala.collection.immutable.SortedMap("Alice" -> 10, "Fred" -> 7, "Bob" -> 3, "Cindy" -> 8)
scores: scala.collection.immutable.SortedMap[String,Int] = Map(Alice -> 10, Bob -> 3, Cindy -> 8, Fred -> 7)

可变的树形映射,选择 Java 的 TreeMap。

按照插入顺序访问所有的键,使用 scala 的 LinkedHashMap

4.6 与 Java 的互操作

Java映射 转换为 Scala映射

scala> import scala.collection.JavaConversions.mapAsScalaMap
import scala.collection.JavaConversions.mapAsScalaMap

scala> val scores: scala.collection.mutable.Map[String, Int] = new java.util.TreeMap[String, Int]
scores: scala.collection.mutable.Map[String,Int] = Map()

java.util.Properties 到 Map[String, String] 的转换

scala> import scala.collection.JavaConversions.propertiesAsScalaMap
import scala.collection.JavaConversions.propertiesAsScalaMap

scala> val props: scala.collection.Map[String, String] = System.getProperties()
props: scala.collection.Map[String,String] =
Map(env.emacs -> "", java.runtime.name -> Java(TM) SE Runtime Environment, sun.boot.library.path -> /Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib, java.vm.version -> 24.79-b02, user.country.format -> CN, gopherProxySet -> false, java.vm.vendor -> Oracle Corporation, java.vendor.url -> http://java.oracle.com/, path.separator -> :, java.vm.name -> Java HotSpot(TM) 64.Bit Server VM, file.encoding.pkg -> sun.io, user.country -> US, sun.java.launcher -> SUN_STANDARD, sun.os.patch.level -> unknown, java.vm.specification.name -> Java Virtual Machine Specification, user.dir -> /Users/hp, java.runtime.version -> 1.7.0_79-b15, java.awt.graphicsenv -> sun.awt.CGraphicsEnvironment, java.endorsed.dirs -> /Library/Java/JavaVirtual...

Scala映射 传递给 预期 Java映射 的方法,提供相反的隐式转换

import scala.collection.JavaConversions.mapAsJavaMap
import java.awt.font.TextAttribute._

scala> val attrs = Map(FAMILY -> "Serif", SIZE -> 12)
attrs: scala.collection.immutable.Map[java.awt.font.TextAttribute,Any] = Map(java.awt.font.TextAttribute(family) -> Serif, java.awt.font.TextAttribute(size) -> 12)

以下方法预期一个 java映射
scala> val font = new java.awt.Font(attrs)
font: java.awt.Font = java.awt.Font[family=Serif,name=Serif,style=plain,size=12]

scala> font
res47: java.awt.Font = java.awt.Font[family=Serif,name=Serif,style=plain,size=12]

scala> attrs
res48: scala.collection.immutable.Map[java.awt.font.TextAttribute,Any] = Map(java.awt.font.TextAttribute(family) -> Serif, java.awt.font.TextAttribute(size) -> 12)

不是很明白 java互操作?

4.7 元组

映射是 key/value 对偶 的集合。 对偶 是 元组 tuple 最简单的形态。

scala> val t = (1, 3.14, "Hello")
t: (Int, Double, String) = (1,3.14,Hello)

scala> val second = t._2
second: Double = 3.14

scala> val (first, second, _) = t
first: Int = 1
second: Double = 3.14

元组的下标从 1 开始。

元组可以用于函数返回不止一个值的情况。举例来说, StringOps 的 partition 方法返回的是一对字符串,分别包含了满足某个条件和不满足该条件的字符

scala> val y1 = "New York".partition(_.isUpper)
y1: (String, String) = (NY,ew ork)

4.8 拉链操作

使用元组的原因之一是把多个值绑在一起,便于它们被一起处理。

scala> val symbols = Array("<", "-", ">")
symbols: Array[String] = Array(<, -, >)

scala> val counts = Array(2, 10, 2)
counts: Array[Int] = Array(2, 10, 2)

scala> val pairs = symbols.zip(counts)
pairs: Array[(String, Int)] = Array((<,2), (-,10), (>,2))

scala> for ((s, n) <- pairs) Console.print(s * n)
<<---------->>

用 toMap 方法可以将对偶的集合转换成映射

scala> val pairs = symbols.zip(counts).toMap
pairs: scala.collection.immutable.Map[String,Int] = Map(< -> 2, - -> 10, > -> 2)

blair
209 声望31 粉丝

我是 Blair


引用和评论

0 条评论