朝花夕拾

不要以今天的回避来逃脱明天的责任。

Scala之旅-解析(FOR COMPREHENSIONS)和泛型类(GENERIC CLASSES)

解析(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多岁用户的守卫条件。

这是使用两个生成器的更复杂的例子。它计算 0n-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 == 10v == 10。第一次迭代时,i = 0j = 0i + 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) ...

注意解析不局限于列表。每一个支持 withFiltermapflatMap 操作的数据类型适当的类型)都可以用于序列解析。
你可以在解析器中省略 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)

这里写图片描述
AppleBanana 都继承自 Fruit,因此我们可以将 applebanana 实例添加进 Stack[Fruit] 中。

注意:泛型类型的子类型是不变的(invariant)。这意味着如果我们有一个字符栈 Stack[Char],那么字符栈里内容不可以被使用在整型栈 Stack[Int] 中。这是有缺点的,因为我们可以将纯整数添加到字符栈里。总结:当且仅当 B = A 时,StackA] 才是 Stack[B] 的子类型。由于这种情况非常具有局限性,因此 Scala 提供了类型参数注解机制(type parameter annotation mechanism) 来控制泛型类型的子类型行为。

阅读更多
个人分类: scala
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

Scala之旅-解析(FOR COMPREHENSIONS)和泛型类(GENERIC CLASSES)

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭