Scala型变: 不变、协变、逆变

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>();      // 错误

    }

总结

所有的类型编程都必须要在编译时确定是否合法,因为类型信息只有在编译时才是可知的(编译后会丢失),最后它们的关系图如下。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值