javaweb
javaweb copied to clipboard
scala 学习(五)
类型和多态
类型
- 参数化多态性 粗略地说,就是泛型编程
- (局部)类型推断 粗略地说,就是为什么你不需要这样写代码val i: Int = 12: Int
- 存在量化 粗略地说,为一些没有名称的类型进行定义
- 视窗 我们将下周学习这些;粗略地说,就是将一种类型的值“强制转换”为另一种类型
变性 Variance
Scala的类型系统必须同时解释类层次和多态性。类层次结构可以表达子类关系。在混合OO和多态性时,一个核心问题是:如果T’是T一个子类,Container[T’]应该被看做是Container[T]的子类吗?变性(Variance)注解允许你表达类层次结构和多态类型之间的关系:
含义 | Scala 标记 | |
---|---|---|
协变covariant | C[T’]是 C[T] 的子类 | [+T] |
逆变contravariant | C[T] 是 C[T’]的子类 | [-T] |
不变invariant | C[T] 和 C[T’]无关 | [T] |
当我们定义一个协变类型List[A+]时,List[Child]可以是List[Parent]的子类型。
当我们定义一个逆变类型List[-A]时,List[Child]可以是List[Parent]的父类型。
Scala的协变
scala> class Student{}
defined class Student
scala> class BadStudent extends Student
defined class BadStudent
scala> class Covariant[T](t:T)
defined class Covariant
scala> val cov = new Covariant[BadStudent](new BadStudent())
cov: Covariant[BadStudent] = Covariant@2aaf152b
scala> val cov2:Covariant[Student] = cov
<console>:14: error: type mismatch;
found : Covariant[BadStudent]
required: Covariant[Student]
Note: BadStudent <: Student, but class Covariant is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
val cov2:Covariant[Student] = cov
^
scala> class Covariant[+T](t:T){}
defined class Covariant
scala> val cov = new Covariant[BadStudent](new BadStudent)
cov: Covariant[BadStudent] = Covariant@708eb310
scala> val cov2:Covariant[Student] = cov
cov2: Covariant[Student] = Covariant@708eb310
Scala的逆变
scala> class Contravariant[-T](t: T)
defined class Contravariant
scala> val ct = new Contravariant[BadStudent](new BadStudent())
ct: Contravariant[BadStudent] = Contravariant@1a3e8e24
scala> val ct2:Contravariant[Student] = ct
<console>:14: error: type mismatch;
found : Contravariant[BadStudent]
required: Contravariant[Student]
val ct2:Contravariant[Student] = ct
^
scala> val ct3 = new Contravariant[Student](new Student())
ct3: Contravariant[Student] = Contravariant@77bc2e16
scala> val ct4:Contravariant[BadStudent] = ct3
ct4: Contravariant[BadStudent] = Contravariant@77bc2e16
边界
下界lower bounds
scala> def cacophony[T](things: Seq[T]) = things map (_.sound)
<console>:7: error: value sound is not a member of type parameter T
def cacophony[T](things: Seq[T]) = things map (_.sound)
^
scala> def biophony[T <: Animal](things: Seq[T]) = things map (_.sound)
biophony: [T <: Animal](things: Seq[T])Seq[java.lang.String]
scala> biophony(Seq(new Chicken, new Bird))
res5: Seq[java.lang.String] = List(cluck, call)
类型下界也是支持的,这让逆变和巧妙协变的引入得心应手。List[+T]是协变的;一个Bird的列表也是Animal的列表。List定义一个操作::(elem T)返回一个加入了elem的新的List。新的List和原来的列表具有相同的类型:
scala> val flock = List(new Bird, new Bird)
flock: List[Bird] = List(Bird@7e1ec70e, Bird@169ea8d2)
scala> new Chicken :: flock
res53: List[Bird] = List(Chicken@56fbda05, Bird@7e1ec70e, Bird@169ea8d2)
List 同样 定义了::[B >: T](x: B) 来返回一个List[B]。请注意B >: T,这指明了类型B为类型T的超类。这个方法让我们能够做正确地处理在一个List[Bird]前面加一个Animal的操作:
scala> new Animal :: flock
res59: List[Animal] = List(Animal@11f8d3a8, Bird@7e1ec70e, Bird@169ea8d2)
View Bound <%
Scala还有一种视图绑定的功能,如
class Bird {def sing = {}}
class Toy {}
class Consumer[T <% Bird]() {
def use(t: T) = t.sing
}
或者类型参数在方法上:
class Bird {def sing = {}}
class Toy {}
class Consumer() {
def use[T <% Bird](t: T) = t.sing
}
class Test extends App {
val c = new Consumer()
c.use(new Toy)
}
它要求T必须有一种隐式转换能转换成Bird,也就是 T => Bird,否则上面的代码会编译出错: No implicit view available from Toy => Bird. 加入一个隐式转换,编译通过。
import scala.language.implicitConversions
class Bird {def sing = {}}
class Toy {}
class Consumer() {
def use[T <% Bird](t: T) = t.sing
}
class Test extends App {
implicit def toy2Bird(t: Toy) = new Bird
val c = new Consumer()
c.use(new Toy)
}
Context Bound
context bound在Scala 2.8.0中引入,也被称作type class pattern。 view bound使用A <% String方式,context bound则需要参数化的类型,如Ordered[A]。 它声明了一个类型A,隐式地有一个类型B[A],语法如下:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
更清晰的一个例子:
def f[A : ClassManifest](n: Int) = new Array[A](n)
又比如
`def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)`
其他类型限制
方法可以通过隐含参数执行更复杂的类型限制。例如,List支持对数字内容执行sum,但对其他内容却不行。可是Scala的数字类型并不都共享一个超类,所以我们不能使用T <: Number。相反,要使之能工作,Scala的math库对适当的类型T 定义了一个隐含的Numeric[T]。 然后在List定义中使用它:
sum[B >: A](implicit num: Numeric[B]): B
如果你调用List(1,2).sum(),你并不需要传入一个 num 参数;它是隐式设置的。但如果你调用List("whoop").sum(),它会抱怨无法设置num。
在没有设定陌生的对象为Numeric的时候,方法可能会要求某种特定类型的“证据”。这时可以使用以下类型-关系运算符:
A =:= B A 必须和 B相等 A <:< B A 必须是 B的子类 A <%< B A 必须可以被看做是 B
scala> class Container[A](value: A) { def addIt(implicit evidence: A =:= Int) = 123 + value }
defined class Container
scala> (new Container(123)).addIt
res11: Int = 246
scala> (new Container("123")).addIt
<console>:10: error: could not find implicit value for parameter evidence: =:=[java.lang.String,Int]
类似地,根据之前的隐式转换,我们可以放松约束为可视性:
scala> class Container[A](value: A) { def addIt(implicit evidence: A <%< Int) = 123 + value }
defined class Container
scala> (new Container("123")).addIt
res15: Int = 246
使用视图进行泛型编程
在Scala标准库中,视图主要用于实现集合的通用函数。例如“min”函数(在 Seq[] 上)就使用了这种技术:
def min[B >: A](implicit cmp: Ordering[B]): A = {
if (isEmpty)
throw new UnsupportedOperationException("empty.min")
reduceLeft((x, y) => if (cmp.lteq(x, y)) x else y)
}
其主要优点是:
- 集合中的元素并不是必须实现 Ordered 特质,但 Ordered 的使用仍然可以执行静态类型检查。
- 无需任何额外的库支持,你也可以定义自己的排序:
scala> List(1,2,3,4).min
res0: Int = 1
scala> List(1,2,3,4).min(new Ordering[Int] { def compare(a: Int, b: Int) = b compare a })
res3: Int = 4
作为旁注,标准库中有视图来将 Ordered 转换为 Ordering (反之亦然)。
trait LowPriorityOrderingImplicits {
implicit def ordered[A <: Ordered[A]]: Ordering[A] = new Ordering[A] {
def compare(x: A, y: A) = x.compare(y)
}
}