参数化类型在静态的,面向对象的语言中很常见。除了参数化类型,Scala 还支持抽象类型,这在函数式语言中很常见。
// 抽象类型用 type 关键字声明
trait exampleTrait {
type t1 // 一个普通的没有约束的抽象类型
type t2 >: t3 <: t1 // t2 必须是 t3 的父类,t1 的子类
type t3 <: t1 // t3 必须是 t1 的子类
type t4 <: Seq[t1] // t4 必须是元素为 t1 的序列的子类
// type t5 = +AnyRef // ❌错误 不能在此使用变异标记
val v1: t1 // t1 定义后才能初始化
val v2: t2 // 同上
...
}
这里声明了 t1,t2,t3 的关系:
定义一些 trait 和一个类来测试一下这些类型:
trait T1 { val name1: String }
trait T2 extends T1 { val name2: String }
case class C(name1: String, name2: String) extends T2
最后,我们可以定义一个具体类型,定义抽象类型成员,并初始化相应的值:
object example extends exampleTrait {
type t1 = T1
type t2 = T2
type t3 = C
type t4 = Vector[T1]
val v1 = new T1 { val name1 = "T1"}
val v2 = new T2 { val name1 = "T1"; val name2 = "T2"
val v3 = C("1", "2")
val v4 = Vector(C("3", "4"))
}
什么时候使用何种类型
从技术上讲,参数化类型和抽象类型之间可以相互替换,你可以用参数化类型表示任意抽象类型,反之亦然。然而在事件中,不同的特征适合处理不同的设计问题。
参数化类型可以很好的用于容器中,如集合。而类型参数所表示的元素类型与容器本身并没有什么联系。例如:List[String], List[Int], List[Double] 的工作方式相同。
如果换成抽象类型会如何?以 Some 容器为例:
// 原始声明: case final class Some[+A](val value : A) { ... } // 如果换成抽象类型: case final class Some(val value : ???) { type A ... }
参数 value 应该用什么类型呢?我们不能使用 A,因为它不在构造器参数列表的作用域内。 我们虽然可以用 Any,但这样就违背了类型安全的目的。
所以,当类型的参数用于构造器时,唯一合适的选择就是参数化类型。
反过来,抽象类型在相互密切联系的 “类型家族” 中也非常有用。
import java.io._ abstract class BulkReader { type In val source: In def read: String // Read and return a String } class StringBulkReader(val source: String) extends BulkReader { type In = String def read: String = source } class FileBulkReader(val source: File) extends BulkReader { type In = File def read: String = {...} }
- 可以看出,此时类如何运作取决于传入的类型(String 还是 File),所以此时,抽象类型是不二选择