泛型用于指定类或方法可以接受任意类型参数,参数在实际使用时才被确定,泛型可以有效地增强程序的适用性,使用泛型可以使得类或方法具有更强的通用性。泛型的典型应用场景是集合及集合中的方法参数,可以说同 Java 一样,Scala 中泛型无处不在,具体可查看 Scala 的 API
泛型基础
泛型,即“参数化类型”,就是将类型由原来的具体类型参数化,然后在使用/调用时传入具体的类型。泛型的本质是为了参数化类型,在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、特质和方法中,分别被称为泛型类、泛型方法、泛型特质。
泛型类:指定类可以接受任意类型参数。在创建对象时, 可以指定具体的类型,传参值要与指定类型一致,或者不指定类型,基于传参值自动推断类型。
// 定义泛型类 Student ,泛型 T 和 N
class Student[T,N](name:T,var age:N) {
def show():Unit = {
println(s"name:$name, age:$age")
}
}
// 传参值要写入泛型指定的类型
val a = new Student[String,Int]("张三",33)
a.show()
// 不指定类型,自动推断类型
val b = new Student("李四","六十")
b.show()
泛型方法:指定方法可以接受任意类型参数。在调用方法时, 可以明确具体的数据类型,或者不指定类型,基于传参值自动推断类型。
// 定义泛型方法show
def show[T](x:T):T = { x }
// 指定了方法类型
val a = show[String]("hi")
// 自动推断类型
val b = show(33)
val c = show(9.9)
泛型特质:指的是把泛型定义到特质的声明上, 即:该特质中的成员的参数类型是由泛型来决定的. 在定义泛型特质的子类或者子单例对象时, 明确具体的数据类型。
// 泛型特质 Person
trait Person[T]{
def show():T
}
// 泛型类 Student , 继承指定为String类型的特质
class Student[N](name:String,age:N) extends Person[String]{
override def show():String = {
"name:"+name+", age:"+age
}
}
// 参数要写入泛型定义的指定的类型,不指定泛型,会基于传递的参数值自动推断类型
val a = new Student[Int]("张三",33)
a.show()
val b = new Student("李四","六十")
b.show()
泛型标记符:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(一般类型)
K - Key(键)
V - Value(值)
N - Number(数值类型)
_ - 表示不确定的类型
类型变量界定
类型变量界定是指在泛型的基础上,对泛型的范围进行进一步的界定,从而缩小泛型的具体范围,即限定该泛型必须从哪个类继承、或者必须是哪个类的父类。这就是泛型的上界与下界。
上界 :使用 [T <: 类型] 表示给类型添加一个上界,表示泛型参数必须是从该类型本身或该类型的子类型。
下界 :使用 [T >: 类型] 表示给类型添加一个下界,表示泛型参数必须是从该类型本身或该类型的父类型。
示例:
// T 必须继承自实现了Comparable的类, 才能执行compareTo方法,否则报错
def compare[T <: Comparable[T]](x:T, y:T) = {
if(x.compareTo(y) > 0) x else y
}
compare("A", "B")
视图界定
上面讲的类型变量界定建立在类继承层次结构的基础上,但有时候这种限定不能满足实际要求,如果希望跨越类继承层次结构时,可以使用视图界定来实现,其原理是通过隐式转换来实现。
隐含参数和方法也可以定义隐式转换,称作视图。视图的绑定从另一个角度看就是 implicit 的转换。主要用在两个场合:① 当一个 T 类型的变量 t 要装换成 A 类型时。 ② 当一个类型 T 的变量 t 无法拥有 A 类型的 a 方法或变量时。其实视图的绑定是为了更方便的使用隐式装换,视图界定利用 <% 符号来实现。
示例代码
// 除可以接受 String 及其子类外,也可以接受可以隐式转换为 String 类型的类型
def concat[T <% String](x:T, y:T):String = { x + y }
// 定义一个 Int 转换 String 的隐式函数
implicit def intToString(i:Int) = i.toString
// Int类型会隐式转换为String类型,然后做字符串拼接
concat(165,173)
协变、逆变与不变
协变(+)
C[+T]:当类型 B 是类型 A 的子类型时,则 C[B] 也可以认为是 C[A] 的子类型,即 C[B] 可以泛化为 C[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变 (covariance) 。比如 List[+T]。
class Person //父类
class Student extends Person //子类
class Covariance[+T] //协变
val a:Covariance[Person] = new Covariance[Student] // Covariance[Person] 是 Covariance[Student] 的父类
逆变(-)
C[-T]:当类型 B 是类型 A 的子类型,则 C[A] 反过来可以认为是 C[B] 的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变(contravariance) 。比如Queue[-T]。
class Person //父类
class Student extends Person //子类
class Contravariance[-T] //逆变
val b:Contravariance[Student] = new Contravariance[Person] // Contravariance[Person] 是 Contravariance[Student] 的子类
不变
C[T]:无论类型 B 与类型 A 是什么关系, C[A] 和 C[B] 没有从属关系,称为不变(invariance) 。
class Person //父类
class Student extends Person //子类
class Invariance[T] //不变
val a:Invariance[Person] = new Invariance[Student] // Invariance[Person] 与 Invariance[Student] 没有从属关系
val b:Invariance[Student] = new Invariance[Person] // Invariance[Person] 与 Invariance[Student] 没有从属关系