Scala Note – 泛型

今天在看spark代码时,发现了spark代码中使用了大量的泛型。非OOD程序员出身表示压力很大。本文就简单整理一下Scala中的泛型。

1. 泛型类

在类型后使用中括号表示为一个泛型类(Java中是尖括号<>),如下面RDD抽象类。那么在定义RDD的对象时可以指定ClassTag类型。

RDD的子类中使用泛型,将U的类型传递给父类。

在定义时使用泛型的参数,示例如下:

另外,trait,case class和函数也可以使用泛型。

2. 泛型函数和自动推断

3. 不能过于泛化

有一些你想表达的类型概念“过于泛化”以至于编译器无法理解。假设你有一个函数

这段定义可以通过。
你希望继续泛型地使用它,即将泛型类型A传递给自身的变量f

这段代码不能编译,因为所有的泛型类型变量只有在实际调用上下文中才被固定。即使你“钉住”了类型B:

4. lower bound和upper bound

在泛型中,可以使用<:定义了一个upper bound,表示左侧的泛型类型必须是右侧类型的类或者其子类,类似lower bound使用>:来定义,表明左侧必须是右侧的超类。
例如

这说明codec变量的类必须是CompressionCodec类或者其子类。
又如下面Comp类必须接受实现了java.lang.Comparable接口的类型,以保证compare(类型实现了compareTo方法)可用。

view bound符号>%,<%表示左侧类型不是右边类型的子类,但是可以隐式转换到右侧类型的父类/子类。例如Int类型没有实现java.lang.Comparable接口,但是可以隐式转换称为实现java.lang.Comparable接口的RichInt,从而放宽了类型的限制。

在SparkContext.scala中可以看见有大量org.apache.hadoop.ioWritable接口的view bound:

5. 变性 Variance(Scala School)

在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]

子类型关系的真正含义:对一个给定的类型T,如果T’是其子类型,你能替换它吗?

逆变似乎很奇怪。什么时候才会用到它呢?令人惊讶的是,函数特质的定义就使用了它!

如果你仔细从替换的角度思考一下,会发现它是非常合理的。让我们先定义一个简单的类层次结构:

假设你需要一个以Bird为参数的函数:

标准动物库有一个函数满足了你的需求,但它的参数是Animal。在大多数情况下,如果你说“我需要一个___,我有一个___的子类”是可以的。但是,在函数参数这里是逆变的。如果你需要一个接受参数类型Bird的函数变量,但却将这个变量指向了接受参数类型为Chicken的函数,那么给它传入一个Duck时就会出错。然而,如果将该变量指向一个接受参数类型为Animal的函数就不会有这种问题:

函数的返回值类型是协变的。如果你需要一个返回Bird的函数,但指向的函数返回类型是Chicken,这当然是可以的。

参考:
Spark源代码
Scala School

Posted in Dev, Java|Scala.