泛型与类型转换高级应用
泛型与类型转换高级应用
多重界定
多重泛型界定
泛型变量可以同时有上界与下界:
T >: Lower <: Upper
虽然同时有多个上界或下界是不可以的,但可以要求一个类型实现多个特质:
T <: Comparable[T] with Serializable with Coneable
多重视图界定
T <% Comparable[T] <% String
多重上下文界定
T : Ordering : Manifest
类型约束
可用的约束有三种:
-
T =:= U
:T
是否等于U
。 -
T <:< U
:T
是U
的子类型。 -
T <%< U
:T
是否能被隐式转换为U
。
定义只在特定条件下使用的方法
类型约束让程序员定义只有在特定条件下使用的方法。例:
scala> class Pair[T](val first: T, val second: T) { | def smaller(implicit ev: T <:< Ordered[T]) = | if (first < second) first else second | } defined class Pair
可以虽然File
没有混入Ordered[T]
,但因为smaller
声明了隐式参数,所以还是可以
构造出Pair[File]
:
scala> import java.io.File import java.io.File scala> val p = new Pair(new File("."), new File("..")) p: Pair[java.io.File] = Pair@be1fcc
但是如果调用smaller()
方法,那就出错了:
scala> p.smaller // Error <console>:11: error: Cannot prove that java.io.File <:< Ordered[java.io.File]. p.smaller // Error ^
另一个例子:
Option
有一个orNull
方法:
scala> val friends = Map("Fred" -> "Barney") friends: scala.collection.immutable.Map[String,String] = Map(Fred -> Barney) scala> val friendOpt = friends.get("Wilma") // An Option[String] friendOpt: Option[String] = None scala> val friendOrNull = friendOpt.orNull // A String or null friendOrNull: String = null
Java类对象如果为null
表示没有值,但基本类型没有办法用null
表示。
因为orNull
的实现带有约束Null <:< A
,所以可能实例化Option[Int]
,只要别对
这些实例使用orNull
就可以了:
scala> val scores = Map("Fred" -> 42) scores: scala.collection.immutable.Map[String,Int] = Map(Fred -> 42) scala> val scoreOpt = scores.get("Fred") // An Option[Int] scoreOpt: Option[Int] = Some(42) scala> val scoreOrNull = scoreOpt.orNull // Error <console>:9: error: Cannot prove that Null <:< Int. val scoreOrNull = scoreOpt.orNull // Error
改进类型推断
<:<
能增加类型推断,比如对于参数的下界声明:
scala> def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last) firstLast: [A, C <: Iterable[A]](it: C)(A, A)
调用时不能直接传入整数列表,因为实参类型[Nothing, List[Int]]
不符合形参类型
[A, C <: Iterable[A]]
:
scala> firstLast(List(1, 2, 3)) // Error <console>:9: error: inferred type arguments [Nothing,List[Int]] do not conform to method firstLast's type parameter bounds [A,C <: Iterable[A]] firstLast(List(1, 2, 3)) // Error ^ <console>:9: error: type mismatch; found : List[Int] required: C firstLast(List(1, 2, 3)) // Error
因为要在同一步中匹配类型A
与类型C
,仅根据List(1,2,3)
无法判断出A
的类型。
解决方案可以通过柯里化的方式先匹配C
再匹配A
:
scala> def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = | (it.head, it.last) firstLast: [A, C](it: C)(implicit ev: <:<[C,Iterable[A]])(A, A) scala> firstLast(List(1, 2, 3)) // OK res3: (Int, Int) = (1,3)
再看一个类似的例子,corresponds
方法检查两个序列的成员是否一一对应:
scala> val a = Array("Hello", "Fred") a: Array[String] = Array(Hello, Fred) scala> val b = Array(5, 4) b: Array[Int] = Array(5, 4) scala> a.corresponds(b)(_.length == _) res7: Boolean = true
corresponds
方法的声明其实是这样:
def corresponds[B](that: Seq[B])(p: (A, B) => Boolean): Boolean
对于柯里化后的两个参数列表,要推断类型B
:
scala> a.corresponds(b)(_.length == _)
就相当于是:
Array("Hello", "Fred").corresponds(Array(5, 4))(_.length == _)
-
Array[A]
对应前实例Array("Hello","Fred")
,那类型A
就是String
。 -
形参
Seq[B]
得到的实参是Array(5, 4)
,那类型B
就是Int
。 -
确认了
A
与B
,那(A, B) => Boolean
就是(String, Int) => Boolean
。
类型证明
如果firstLast()
方法要返回一个对象的头和尾,最直接的逻辑是:
def firstLast[C](it: C) = (it.head, it.last)
但这样是错误的,因为不知道it
的类型C
是什么类型,很有可能它没有head
与last
方法。
如果用T
表示有head
与last
方法的类型(比如Iterable
特质)。需要确保C
类型
必须继承或是可以隐式转换为T
:
def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.last)
en
就是类型证明对象,它的类型是C <:< Iterable[A]
,保证C
是Iterable[A]
的
类型(或是子类)或可以转换为Iterable[A]
。这里不知道Iterable
成员的类型是啥,
就用Iterable[A]
来表示。
但=:=
、<:<
、<%<
其实是库中的类而不是语法特性,而且是带有隐式值的类。比如在
Predef
对象中<:<
的定义:
abstract class <:<[-From, +To] extends Function1[From,To] object <:< { implicit def conforms[A] = new (A <:< A) { def apply(x: A) = x } }
类型<:<[-From, +To]
继承自函数,参数-From
逆变而返回值+To
协变。这个函数的
功能可以理解为把From
类型从转为To
类型对象。
伴生对象中的方法conforms[A]
是一个隐式的对象。提供了一个把类型From
转为类型To
的默认实现:在这里,它假设To
类型就是From
类型或是它的子类。它直接用apply()
方法返回一个<:<[A,A]
的实例,因为同一个类型即是自己的超类又是自己的子类,所以
返回的<:<[A,A]
符合<:<[-From, +To]
。
现在回到之前取列表头尾的函数,我们用整数列表为参数:
scala> def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = | (it.head, it.last) firstLast: [A, C](it: C)(implicit ev: <:<[C,Iterable[A]])(A, A) scala> firstLast(List(1, 7, 2, 9)) res0: (Int, Int) = (1,9)
在这里编译器要验证的是implicit ev: List[Int] <:< Iterable[Int]
,会先查看在
伴生对象中是否有可以应用到List[Int] <:< Iterable[Int]
的隐式对象。当找到:
implicit def conforms[A] = new (A <:< A) { def apply(x: A) = x }
用List
代入类型A
:
def conforms[List] = new (List <:< List) { def apply(x: List) = x }
的返回类型List <:< List
可以匹配到List <:< Iterable
。因为<:<[-From, +To]
中
参数-From
逆变而返回值+To
协变。
检查泛型隐式对象是否存在
在REPL环境中可以用implicitly
函数检查泛型隐式对象是否存在:
scala> implicitly[String <:< AnyRef] res1: <:<[String,AnyRef] = <function1> scala> implicitly[AnyRef <:< String] <console>:8: error: Cannot prove that AnyRef <:< String. implicitly[AnyRef <:< String] ^
存在会返回函数,不存在就返回错误:
scala> implicitly[List[Int] <:< Iterable[Int]] res4: <:<[List[Int],Iterable[Int]] = <function1> scala> implicitly[List <:< Iterable] <console>:8: error: type List takes type parameters implicitly[List <:< Iterable] ^ <console>:8: error: type Iterable takes type parameters implicitly[List <:< Iterable] ^
implicitNotFount注解
作为是为了让报错时的信息更加有可读性:
@implicitNotFound(msg = "I am baffled why you give me ${From} when I want ${To}.") abstract class <:<[-From, +To] extends Function1[From, To] object <:< { implicit def conforms[A] = new (A <:< A) { def apply(x: A) = x } } def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.last)
这样当出错时:
scala> firstLast("Fred") <console>:23: error: I am baffled why you give me String when I want Iterable[A]. firstLast("Fred") ^
CanBuildFrom解读
定义
模拟Iterable
特质中的map
方法。先定义Iterator
特质:
trait Iterator[E] { def next(): E def hasNext: Boolean }
集合的构造器是Builder
,把E
类型的元素添加到缓存中去。返回的结果是一个集合,
类型用To
表示:
trait Builder[-E, +To] { def +=(e: E): Unit def result(): To }
CanBuildFrom[From, E, To]
特质提供类型证明。它的apply()
方法把From
类型的实例
转为Builder
:
trait CanBuildFrom[-From, -E, +To] { def apply(): Builder[E, To] }
这样就实现的From
与To
的类型兼容。
然后是Iterable
特质,有iterator()
方法返回Iterator
类型的迭代器,map()
方法
执行映射操作:
trait Iterable[A, Repr] { def iterator(): Iterator[A] def map[B, That](f : (A) => B) (implicit bf: CanBuildFrom[Repr, B, That]): That = { val builder = bf() val iter = iterator() while (iter.hasNext) builder += f(iter.next()) builder.result } }
map()
方法的和第一个参数列表是映射的方法F
,很好理解。
第二个参数列表中的类型参数Repr
是展现类型,它可以选择合适的构造器工厂来创建如
range
可String
之类的非常规集合。
在Scala类库中的Iterable
的map()
方法是被定义在TraversableLike[A, Repr]
特质
中的。这样更加常用的Iterable
就不用再带上Repr
这个类型参数了。
归纳
-
map()
方法的主要任务是创造一个目标类型That
的构造器Builder
。 -
迭代源集合,把每个元素传递给映射方法
f
,把f
的返回值放到Builder
。 -
builder.result()
方法返回目标类型的集合。
使用
每个集合实现都在伴生对象中提供一个隐式的CanBuildFrom
对象。比如下面的
简单版的ArrayBuffer
实现,注意数组类型要有上下文界定[E : Manifest]
:
class Buffer[E : Manifest] extends Iterable[E, Buffer[E]] with Builder[E, Buffer[E]] { private var capacity = 10 private var length = 0 private var elems = new Array[E](capacity) def iterator() = new Iterator[E] { private var i = 0 def hasNext = i < length def next() = { i += 1; elems(i - 1) } } def +=(e: E) { if (length == capacity) { capacity = 2 * capacity val nelems = new Array[E](capacity) for (i <- 0 until length) nelems(i) = elems(i) elems = nelems } elems(length) = e length += 1 } def result() = this } object Buffer { implicit def canBuildFrom[E : Manifest] = new CanBuildFrom[Buffer[_], E, Buffer[E]] { def apply() = new Buffer[E] } }
Iterator()
方法返回迭代器;+=()
方法实现添加元素;canBuildFrom()
方法把源类型
Buffer[_]
集合转为成员类型为E
的构造器Buffer[Manifest]
。
scala> val names = new Buffer[String] names: Buffer[String] = Buffer@114069b scala> names += "Fred" scala> names += "Linda" scala> val lengths = names.map(_.length) lengths: Buffer[Int] = Buffer@4c27d525 scala> lengths.map(println(_)) 4 5 res2: Buffer[Unit] = Buffer@1b8f2e35
注意这里的Buffer
类已经有一个+=()
方法了,而且返回类型就是自己。所以可以用它
自己来混入Builder
接口。
相对来说如果我们需要的是一个简化版的Range
类型,那就要注意Range
类的构造函数
并不会返回一个Range
类型实例(当然也不应该这样返回)如:
scala> (1 to 10).map(x => x * x) res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 4, 9, 16, 25, 36, 4 9, 64, 81, 100)
返回的类型应该是一个序列而不是一个Range
。在Scala的类库中Range
是扩展自
IndexedSeq[Int]
,而IndexedSeq
的伴生对象定义一个构造Vector
的构造器。对于
我们的简化版Range
来说,要提供一个Buffer
作为其构造器:
class Range(val low: Int, val high: Int) extends Iterable[Int, Range] { def iterator() = new Iterator[Int] { private var i = low def hasNext = i <= high def next() = { i += 1; i - 1 } } } object Range { implicit def canBuildFrom[E : Manifest] = new CanBuildFrom[Range, E, Buffer[E]] { def apply() = new Buffer[E] } }
注意构造器的类型为Buffer[E]
。
对于map
方法中的CanBuildFrom
隐式参数的定义:
implicit bf: CanBuildFrom[Repr, E, That]
来说Repr
就是Range
,这样隐式参数就可以看作:
implicit bf: CanBuildFrom[Range, E, That]
Range
伴生对象的canBuildFrom[E]
被调用产生的是:
CanBuildFrom[Range, E, Buffer[E]]
上面这个就是bf
的类型,其apply
方法将产出Buffer[E]
,用于构造结果。
总之,隐式参数CanBuildFrom[Repr, E, That]
会定位到一个可以产出目标集合构造器的
工厂对象。这个工厂是定义在Repr
伴生对象中的一个隐式值。
调用时:
scala> import scala.math._ import scala.math._ scala> val res = new Range(1, 10).map(sqrt(_)) res: Buffer[Double] = Buffer@79111260 scala> res.map(println(_))
依赖于其他类型的类型的类型
与上面的CanBuildFrom
类似的,还有一个通过依赖其他类型的实现方案.
List[T]
依赖于类型T
生成一个特定类型的实例,如List[Int]
。有时称这样的泛型
类型为类型构造器(type constructor)。不仅如此,Scala中还可以定义出依赖于其他
类型的类型的类型。
为了明白这样做的意义,我们用一个简化版的Iterable
特质来说明:
trait Iterable[E] { def iterator(): Iterator[E] def map[F](f: (E) => F): Iterable[F] }
如果有一个类实现该特质:
class Buffer[E] extends Iterable[E] { def iterator(): Iterator[E] = .... def map[F](f: (E) => f): Buffer[F] = ... }
因为需要在Buffer
类的map()
方法中返回的是Buffer
类型自己而不是Iterable
,
所以为了Iterable
特质中实现这个map()
方法,我们必须用一个东西来代表Buffer[E]
或是其他的子类:
trait Iterable[E, C[_]] { def iterator(): Iterator[E] def build[F](): C[F] def map[F](f: (E) => F): Iterable[F] }
这里的参数类型C[_]
自己也是一个参数类型,所以像高阶函数一样Iterable
成为了一个
高阶类型。
对于map()
方法返回的类型并不一定和实例原来的类型一样,比如:Buffer
类的map()
方法的返回类型也是Buffer
;但是Range
执行map()
方法的结果通常不会也是一个
Range
(比如可能是一个Buffer[F]
)。所以对于Range
类型声明的可能是这样的:
class Range extends Iterable[Int, Buffer]
这里的Int, Buffer
对应Iterable
声明中的E, C[_]
,显然C[_]
对应的是Buffer
。
现在因为map()
方法返回的类可以是存放任何F
类型的容器,所以我们需要一个类来表示
存放任何F
类型的容器的类Container
:
trait Container[E] { def +=(e: E): Unit }
它正好适合作为Iterable
的build()
方法的返回类型:
trait Iterable[E, C[X] <: Container[X]] { def build[F](): C[F] ... }
这里限制的容器C
和Container
的内容必须是相同类型的。
这样就可以在Iterable
中实现map()
方法了:
trait Iterable[E, C[X] <: Container[X]] { def iterator(): Iterator[E] def build[F : Manifest](): C[F] def map[F : Manifest](f: (E) => F): C[F] = { val res = build[F]() val iter = iterator() while (iter.hasNext) res += f(iter.next()) res } }
这样子类中就不用实现map()
方法了。
下面是Range
类的定义:
// An iterable, but not a container class Range(val low: Int, val high: Int) extends Iterable[Int, Buffer] { def iterator() = new Iterator[Int] { private var i = low def hasNext = i <= high def next() = { i += 1; i - 1 } } def build[F : Manifest]() = new Buffer[F] // Produced collection need not be the same type }
它只混入了Iterable
接口:可以遍历内容,但不能添加内容。
Buffer
则混入了Iterable
与Container
:
class Buffer[E : Manifest] extends Iterable[E, Buffer] with Container[E] { private var capacity = 10 private var length = 0 private var elems = new Array[E](capacity) // See note def iterator() = new Iterator[E] { private var i = 0 def hasNext = i < length def next() = { i += 1; elems(i - 1) } } def build[F : Manifest]() = new Buffer[F] def +=(e: E) { if (length == capacity) { capacity = 2 * capacity val nelems = new Array[E](capacity) // See note for (i <- 0 until length) nelems(i) = elems(i) elems = nelems } elems(length) = e length += 1 } }
Manifest
上下文界定是为了构造Array[E]
所必须的,这和高等类型没有什么关系。
小结
这是一个典型的例子:Iterator
依赖Container
。但Container
不是一个普通的类型,
而是一个制件类型的机制。
在实际应用中,Scala的Iterable
并不是高级类型来实现的,而是用隐式转换实现的一个
对象用于构造目标集合。
存在类型
所有Java类型在Scala中都有对等的概念。一般的类型可以用同名的类型表示,如:
-
Java中的
Pattern
对应Scala里的Pattern
-
Java中的
Iterator<Component>
对应Scala里的Iterator[Component]
但是像是Java里的Iterator<?>
或Iterator<? extends Component>
这样的通配符类型
或是Iterator
这样没有参数的原始类型要用到一种额外的叫作「存在类型」的类型来表示。
存在类型是Scala语言所支持的特性,但实际上它的作用是用于从Scala访问Java类型。主要 用途是当Scala访问Java时能够理解编译器报错的信息。存在类型的通用形式如下:
type forSome { declarations }
type
是任意的Scala类型,declarations
是一个抽象的val
和type
列表。这个定义
可以解读为:声明的变量和类型是存在但未知的,正如类中的抽象成员那样。这个类型进而
被允许引用这些声明的变量和类型,虽然编译器不知道具体是什么类。
看一个具体的例子,Java中的Iterator<?>
可以在Scala中写为:
Iterator[T] forSome { type T }
相当于前面的类型通配符:
Iterator[_]
其实类型通配符就是存在类型的一个语法糖。
Java中的:
Iterator<? extends Component>
在Scala中写为存在类型结合指定上界和下界的方式:
Iterator[T] forSome { type T <: Component }
相当于前面的类型通配符:
Iterator[_ <: Component]
还可以有更加复杂的表示法,如:
map[T, U] forSome { type T; type U <: T }
还可以在forSome
块中使用val
声明因为val
是可以有内部类的。以类型:
import scala.collection.mutable.ArrayBuffer class Network { class Member(val name: String) { // ... } // ... }
为例,可以用这样的形式:
m.Member forSome { val n: Network }
这里就完全等同于类型投影:
Network#member
但也会有更复杂的情况:
def process[M <: n.Member forSome { val n: Network }](m1: M, m2: M) = (m1, m2)
这个方法只接收同一实例子类的成员:
val chatter = new Network val myFace = new Network val fred = chatter.join("Fred") val wilma = chatter.join("Wilma") val barney = myFace.join("Barney") process(fred, wilma) // Ok process(fred, barney) // Error
存在类型对于对于简单的用例来说,可以当forSome
不存在。虽然forSome
语句中的类型
和值是未知的,Scala还是会检查程序是否完备。举例来说,对于以下的Java类:
// This is a Java class with wildcards public class Wild { Collection<?> contents() { Collection<String> stuff = new Vector<String>(); stuff.add("a"); stuff.add("b"); stuff.add("see"); return stuff; } }
如果在Scala中访问这个类,会看到它有一个存在类型:
scala> val contents = (new Wild).contents contents: java.util.Collection[?0] forSome { type ?0 } = [a, b, see]
要看这个集合里有多少元素,可以简单忽略存在定义部分,像平常一样调用size
方法:
scala> contents.size() res0: Int = 3
对于复杂的类型的情况,存在类型会显得笨拙一些。因为没有办法给存在类型命名。以创建
一个可变Scala类型为例,需要用contents
的元素初始化它:
import scala.collection.mutable.Set val iter = (new Wild).contents.iterator val set = Set.empty[???] // what type goes here? 这里要用什么类型? while (iter.hasMore) set += iter.next()
第三行里没有办法给出Java集合里的元素类型名称,所以不能给出set
方法的满足类型。
为了绕过此问题,应该考试如下两种技巧:
-
将存在的类型传入方法时,把类型参数从
forSome
语句移到方法的类型参数中。 在方法体内,可以用这个类型参数来指定本来在forSome
语句中的类型。 -
不要从方法返回存在的类型,而是返回一个带有
forSome
语句中的每个类型的抽象成员 的对象(参见抽象对象一章)。
使用这两个技巧,之前的代码写成这个样子:
import scala.collection.mutable.Set import java.util.Collection abstract class SetAndType { type Elem val set: Set[Elem] } def javaSet2ScalaSet[T](jset: Collection[T]): SetAndType = { val sset = Set.empty[T] // now T can be named! val iter = jset.iterator while (iter.hasNext) sset += iter.next() return new SetAndType { type Elem = T val set = sset } }
综上所棕,对于Scala来说,用存在类型实现不如抽象成员实现更加方便。所以Scala中几乎 不用存在类型。
Scala类型小结
类型 | 语法 | |
---|---|---|
类 或 特质 |
class C ... , trait C ...
|
|
元组 |
(T1, ... , Tn)
|
|
函数类型 |
(T1, ... , Tn) => T
|
|
方法类型 |
(T1, ... , Tn)T
|
编译器内部使用 |
注解 |
T @A
|
|
参数化类型 |
A[T1, ... , Tn]
|
|
单例类型 |
value.type
|
|
类型投影 |
O#I
|
|
复合类型 |
T1 with T2 with ... with Tn { ... }
|
|
中置类型 |
T1 A T2
|
|
存在类型 |
T forSome { type and val }
|
方法类型(T1, ... , Tn)T
(与函数类型相比少了=>
)一般只在编译器内部使用。
在REPL中输入函数类型:
scala> val triple = (x: Int) => 3 * x triple: Int => Int = <function1>
而方法类型是这样的:
scala> def square(x: Int) = x * x square: (x: Int)Int
可以在方法后面加上_
可以转为函数类型:
scala> square _ res0: Int => Int = <function1>
家族多态
就是许多个相关的类,又要共用代码、又要保护类型安全,这个比较难搞。Java的事件处理 是一个典型的例子:
-
有多个不同的事件(如:
ActionEvent
、ChangeEvent
等……) -
每个事件有单独的监听器接口(如:
ActionListener
、ChangeListener
等……)
为了设计一套管理监听器的通用机制,我们先用泛型类型,然后再切换到抽象类型。
Java中每个监听器接口有不同的方法对应事件:actionPerformed
、stateChanged
、
itemStateChanged
等。先把这些方法统一起来:
// Version 1: The event source is an Object import scala.collection.mutable.ArrayBuffer import java.awt.event.ActionEvent trait Listener[E] { def occurred(e: E): Unit }
事件源要有一个监听器的集合,和一个触发这些监听器的方法:
trait Source[E, L <: Listener[E]] { private val listeners = new ArrayBuffer[L] def add(l: L) { listeners += l } def remove(l: L) { listeners -= l } def fire(e: E) { for (l <- listeners) l.occurred(e) } }
在这个基础上,以按钮事件ActionEvent
为例,生成对应的监听器:
trait ActionListener extends Listener[ActionEvent]
Button
类可以混入Source
特质:
class Button extends Source[ActionEvent, ActionListener] { def click() { fire(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "click")) } }
现在Button
类不需要重复监听器管理代码,并且监听器的类型是安全的:只能给按钮加上
ActionEvent
,ChangeListener
。
调用:
scala> val b = new Button b: Button = Button@b11fcc2 scala> b.add(new ActionListener { | def occurred(e: ActionEvent) { | println(e) | } | }) scala> b.click() java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=click,when=0,modifiers=] on $line14.$read$$iw$$iw$$iw$Button@b11fcc2
根据Java中ActionEvent
类的定义,它把事件源设置为this
,但事件源的类型为
Object
。这里可以用自身类型让它也是类型安全的:
trait Event[S] { var source: S = _ } trait Listener[S, E <: Event[S]] { def occurred(e: E): Unit } trait Source[S, E <: Event[S], L <: Listener[S, E]] { this: S => private val listeners = new ArrayBuffer[L] def add(l: L) { listeners += l } def remove(l: L) { listeners -= l } def fire(e: E) { e.source = this // Self-type needed here for (l <- listeners) l.occurred(e) } }
自身类型this: S =>
把事件源都设为this
,不然this
只能是某种Source
,而不一定
是Event[S]
所要求的类型。
定义按钮的例子:
class ButtonEvent extends Event[Button] trait ButtonListener extends Listener[Button, ButtonEvent] class Button extends Source[Button, ButtonEvent, ButtonListener] { def click() { fire(new ButtonEvent) } }
调用:
val b = new Button b.add(new ButtonListener { def occurred(e: ButtonEvent) { println(e + " from " + e.source) } }) b.click()
这里的参数类型太多了,看起来不是很简洁。而且类型Button
是循环依赖的。
如果用抽象类型的话,会好很多:
import scala.collection.mutable.ArrayBuffer import java.awt.event.ActionEvent trait ListenerSupport { type S <: Source type E <: Event type L <: Listener trait Event { var source: S = _ } trait Listener { def occurred(e: E): Unit } trait Source { this: S => private val listeners = new ArrayBuffer[L] def add(l: L) { listeners += l } def remove(l: L) { listeners -= l } def fire(e: E) { e.source = this for (l <- listeners) l.occurred(e) } } }
这样也有限制:不能声明顶级类型。所以这里把所有的类型都放在一个ListenerSupport
类型里面。
然后定义按钮事件与按钮监听器时,就可以把定义包含在一个扩展该特质的模块当中:
object ButtonModule extends ListenerSupport { type S = Button type E = ButtonEvent type L = ButtonListener class ButtonEvent extends Event trait ButtonListener extends Listener class Button extends Source { def click() { fire(new ButtonEvent) } } }
调用的例子,注意要使用时必须引入这个模块:
scala> import ButtonModule._ import ButtonModule._ scala> val b = new Button b: ButtonModule.Button = ButtonModule$Button@48250355 scala> b.add(new ButtonListener { | def occurred(e: ButtonEvent) { | println(e + " from " + e.source) | } | }) scala> b.click() $line6.$read$$iw$$iw$ButtonModule$ButtonEvent@65d0e7e9 from $line6.$read$$iw$$iw$ButtonModule$Button@48250355
注意:虽然这里类型名只用了一个字母:
type S = Button type E = ButtonEvent type L = ButtonListener
但实际上标识符都可以用,而且应该是具有可读性的:
type SourceType = Button type EventType = ButtonEvent type ListenerType = ButtonListener