本文链接:Scala学习(五)——泛型
简介
在Java或者C++里面,像列表(List)这些数据结构,在编写的时候,都不需要指定其中元素的类型,而是构造的时候指定,这一特性就称为泛型。同样,Scala中也提供了泛型,而且功能比Java的泛型更加强大。
类的泛型
可以给一个类添加泛型,这样,在编写的时候就不需要考虑这一类型的具体情况,而是使用一个标记来注明。泛型的定义列表用[]包含,用,分隔,卸载类名之后,构造函数参数列表之前,泛型的具体名称可以任意定义(比如如下代码中的K和V):
| 1 | class Pair[K, V] (val key: K, val value: V) | 
类似构造函数,如果要继承的类包括泛型,那么继承时也需要写泛型的类型:
| 1 | class SimplePair[T] (key: T, value: T) extends Pair[T, T](key, value) | 
不过这里Pair的泛型其实是可以通过构造函数传值的类型猜测出来的,所以可以省略:
| 1 | class SimplePair[T] (key: T, value: T) extends Pair(key, value) | 
这样,类名在声明或者新建实例的时候也需要写明泛型的类型:
| 1 | val p: Pair[Int, String] = new Pair[Int, String](1, "abc") | 
当然,上面这句话的最简写法是这样的,可以猜到的类型和泛型设定都被省略掉了:
| 1 | val p = new Pair(1, "abc") | 
方法的泛型
除了类可以设置泛型,方法也可以设置泛型。写法是在方法名后面加上泛型列表。下面这个方法完成了任意类型T数组的赋值操作(没有实际意义):
| 1 2 3 | def setElem[T] (arr: Array[T], e: T): Unit = {   arr(0) = e } | 
调用时同样可以显式写明泛型类型或者省略:
| 1 2 3 | val a = new Array[Int](1) setElem(a, 0) setElem[Int](a, 1) | 
泛型的上下界
在定义泛型列表的时候可以使用<:和>:规定泛型元素的上下界:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class A class B extends A class C extends B class D extends C class E extends D class T1[T >: B]      // 相当于Java里的super class T2[T <: B]      // 相当于Java里的extends class T3[T >: D <: B] // 同时设置上下界 new T1[A] //new T1[C] 不能通过编译 //new T2[A] 不能通过编译 new T2[C] //new T3[A] 不能通过编译 new T3[B] new T3[C] new T3[D] //new T3[E] 不能通过编译 | 
可以使用with关键字对多个特征进行限定:
| 1 | class T4[T <: B with Ordered[T]]  // 在继承B的同时还要求有可排序的性质 | 
协变和逆变
简而言之,协变和逆变是指类型AClass[T]会随着泛型参数T的继承关系而产生继承关系。声明时只需要在泛型参数前加+或者-就可以获得协变或者逆变特性:
| 1 2 | class T5[+T] // 协变 class T6[-T] // 逆变 | 
以下的实例说明了协变和逆变的继承关系:
| 1 2 3 4 5 6 7 8 | class A class B extends A class C extends B val a: T5[B] = new T5[C]  // T5[C]是T5[B]的子类,和B与C的父子关系相同 //val b: T5[B] = new T5[A]   T5[A]是T5[B]的父类,不能通过编译 val c: T6[B] = new T6[A]  // T6[A]是T6[B]的子类,和B与A的父子关系相反 //val d: T6[B] = new T6[C]   T6[C]是T6[B]的父类,不能通过编译 | 
如果不加协变或者逆变的符号,那么:
| 1 2 | class T7[T] //val e: T7[B] = new T7[A]   T7[B]和T7[A]没有继承关系,不能通过编译 | 
在使用中,协变用于写,比如作为参数,可以输入其子类,逆变用于读,比如作为函数返回值,可以接受其父类。协变和逆变这一设定,是Java所不具有的,体现了Scala的实用性。
可实例化的泛型
大家都知道Java的泛型并不能实例化,尤其是像List.toArray这种方法,还需要自己实例化一个相应数组传参进去,不然就只能返回Object数组。Scala运行在JVM上,也存在这样的问题,不过Scala提供了一种较简便的方法来解决这个问题。下面用一个方法来说明:
| 1 | def singleArray[T : ClassTag](e: T): Array[T] = Array(e) | 
这个方法输入一个任意类型的值,输出一个相应的只有一个元素数组。值得注意的是,在泛型参数T之后增加了:ClassTag。这一操作使调用函数时,会将泛型的类型信息记录在一个ClassTag类型的对象里,之后可以通过这个对象调用Java反射构造数组。有关这种写法的意义将在以后说明。
总结
Scala的泛型系统相比Java完善了许多,相比鸡肋一般的Java泛型,Scala泛型的完成度几乎可以媲美C++模板。用好泛型可以使程序可扩展性更强,结构更加严密。