Scala型变
Scala 语言中不变、协变、逆变是指拥有泛型的类型,在声明和赋值时的对应关系
不变:声明时泛型是什么类型,赋值时也只能是什么类型
协变:声明时泛型是父类,赋值时泛型可以是父类也可以是子类
逆变:声明时泛型是子类,赋值时泛型可以是子类也可以是父类
不变
不变是指高阶类型的参数不能改变,所以,这当然是满足型变的,完全合法,如下:
val ln: List[String] = List("yiifaa")
# 参数一致,赋值显然是合法的
val l: List[String] = ln
协变
协变是指把类型参数转换为参数的父类,创建方法是类型参数前加个“+”号,无需额外操作,如下:
case class Person[+T](username: T)
// 声明类型变量
val yiifaa: Person[String] = Person("yiifaa")
val yiifee: Person[Any] = yiifaa
如果不加“+”号会怎样?那就会产生类型不匹配错误,不能适应协变,只能“不变”了。
逆变
逆变是相对于协变而言的,就是把类型参数转换为参数的子类,创建方法是在类型参数前加个“-”。将父类转换为子类是存在风险的,所以还需要添加转换函数,如下:
class Person[-T](username: T) {
// 逆变时参数要放在函数上
def apply(username: T): Person[String] = new Person(username)
}
val yiifee: Person[Any] = new Person("yiifaa")
// 逆变发生了
val yiifaa: Person[String] = yiifee
逆变主要应用于Function对象,其他地方较少。
完整代码实例
// Dog 类继承自 Animal 类
class Animal
class Dog extends Animal
// 声明拥有泛型的类,泛型前面有一个'加号'
class MyList1[+V]
// 声明拥有泛型的类,泛型前面有一个'减号'
class MyList2[-V]
// 声明拥有泛型的类,泛型前面'没有符号'
class MyList3[V]
def main(args: Array[String]): Unit = {
// 协变类型声明时泛型是父类,赋值时泛型可以是父类,也可以是子类
val list1_1: MyList1[Animal] = new MyList1[Dog]() // 正确
val list1_2: MyList1[Animal] = new MyList1[Animal]() // 正确
val list1_3: MyList1[Dog] = new MyList1[Animal]() // 出错
// 逆变类型声明时泛型是子类,赋值时泛型可以是子类,也可以是父类
val list2_2: MyList2[Dog] = new MyList2[Dog]() // 正确
val list2_3: MyList2[Dog] = new MyList2[Animal]() // 正确
val list2_1: MyList2[Animal] = new MyList2[Dog]() // 出错
// 不变类型声明时泛型是子类,赋值时泛型只能是子类
val list3_2: MyList3[Dog] = new MyList3[Dog]() // 正确
val list3_1: MyList3[Animal] = new MyList3[Dog]() // 出错
val list3_3: MyList3[Dog] = new MyList3[Animal]() // 出错
}
对比Java
Java 中只有不变,没有协变也没有不变,声明时泛型是什么类型,赋值时泛型也必须是什么类型
示例代码如下:
class Animal {}
class Dog extends Animal {}
static class MyList<X extends Animal> {}
public static void main(String[] args) {
MyList<Animal> m1 = new MyList<Animal>(); // 正确
MyList<Dog> m2 = new MyList<Dog>(); // 正确
MyList<Animal> m3 = new MyList<Dog>(); // 错误
MyList<Dog> m4 = new MyList<Animal>(); // 错误
}
总结
所有的类型编程都必须要在编译时确定是否合法,因为类型信息只有在编译时才是可知的(编译后会丢失),最后它们的关系图如下。