解析(FOR COMPREHENSIONS)
Scala 为表达序列解析提供了轻量级的符号。 解析器具有 for (enumerators) yield e
形式,其中 enumerators
是一个以分号分隔的枚举器列表。一个枚举器要么是一个引入新变量的生成器,要么是一个过滤器。解析器对枚举器生成的每一个绑定的主体 e
进行评估计算,并返回这些值的序列。
看下面这个例子:
case class User(val name: String, val age: Int)
val userBase = List(new User("Travis", 28),
new User("Kelly", 33),
new User("Jennifer", 44),
new User("Dennis", 23))
val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30))
yield user.name // i.e. add this to a list
twentySomethings.foreach(name => println(name)) // prints Travis Dennis
与 yield
语句一起使用的 for
循环实际上会创建一个 List
。因为我们说过 yield user.name
,它是一个 List[String]
,user <- userBase
是一个生成器,
if (user.age >=20 && user.age < 30)
是一个用于过滤出20多岁用户的守卫条件。
这是使用两个生成器的更复杂的例子。它计算 0
到 n-1
之间数据对中两数之和与 v
相等的全部数据对。
def foo(n: Int, v: Int) =
for (i <- 0 until n;
j <- i until n if i + j == v)
yield (i, j)
foo(10, 10) foreach {
case (i, j) =>
print(s"($i, $j) ") // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5)
}
这里 n == 10
,v == 10
。第一次迭代时,i = 0
,j = 0
则 i + j != v
,因此没有获取到正确的数据对。在 i
增加到 1
之前,j
会循环增加到 9
。如果没有 if
条件语句,那么将会打印如下内容:
(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 1) ...
注意解析不局限于列表。每一个支持 withFilter
、map
和 flatMap
操作的数据类型适当的类型)都可以用于序列解析。
你可以在解析器中省略 yield
。这种情况下,解析器将会返回 Unit
。这对于你需要执行副作用(side-effects)的时候会很有用。
下面是一个和上面例子等价的程序,但没有使用 yield
:
def foo(n: Int, v: Int) =
for (i <- 0 until n;
j <- i until n if i + j == v)
print(s"($i, $j)")
foo(10, 10)
泛型类(GENERIC CLASSES)
泛型类是以类型作为参数的类。它们对于集合类非常有用。
定义泛型类
泛型类将类型作为方括号 [ ] 内的参数。尽管任何参数名都可作为类型参数标识符,但有一种约定是使用 A
作为类型参数标识符。
class Stack[A] {
private var elements: List[A] = Nil
def push(x: A) { elements = x :: elements }
def peek: A = elements.head
def pop(): A = {
val currentTop = peek
elements = elements.tail
currentTop
}
}
Stack
类的实现把任意类型 A
作为参数。这意味着基础列表 var elements: List[A] = Nil
只能存储类型为 A
的元素。def push
程序只能接受类型为 A
的对象(注意:elements = x :: elements
是将elements
重新分配到一个新的列表,该列表是通过将 x
添加到当前 elements
创建的)。
用法
要使用泛型类,请将实际类型替换掉方括号中的 A
。
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
println(stack.pop) // prints 2
println(stack.pop) // prints 1
实例 stack
仅可以使用 Int
类型。但是,如果类型参数有子类型,也可以传入子类型:
class Fruit
class Apple extends Fruit
class Banana extends Fruit
val stack = new Stack[Fruit]
val apple = new Apple
val banana = new Banana
stack.push(apple)
stack.push(banana)
Apple
和 Banana
都继承自 Fruit
,因此我们可以将 apple
和 banana
实例添加进 Stack[Fruit]
中。
注意:泛型类型的子类型是不变的(invariant)。这意味着如果我们有一个字符栈 Stack[Char]
,那么字符栈里内容不可以被使用在整型栈 Stack[Int]
中。这是有缺点的,因为我们可以将纯整数添加到字符栈里。总结:当且仅当 B = A
时,StackA]
才是 Stack[B]
的子类型。由于这种情况非常具有局限性,因此 Scala 提供了类型参数注解机制(type parameter annotation mechanism) 来控制泛型类型的子类型行为。