本文链接: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++模板。用好泛型可以使程序可扩展性更强,结构更加严密。