【Scala笔记——道】Scala 逆变与协变

本文深入探讨了Scala中的逆变与协变概念,解释了它们如何在Function和List等集合类型中工作,以及如何影响类型参数的使用。逆变使泛型返回类型更加灵活,协变则放宽了参数类型的限制。

Function中的逆变与协变

实际上在方法中调用

List(1, 2, 3, 4) map (i => i + 3)

相当于

val f: Int => Int = new Function1[Int,Int] {
def apply(i: Int): Int = i + 3
}
List(1, 2, 3, 4) map (f)
// 结果: List(4, 5, 6, 7)

f: A => B 实际上是 Function的语法糖

class CSuper {
    val s = "s"
    def msuper() = println("CSuper")
}

class C
        extends CSuper {
    val c = "c"
    def m() = println("C")
}

class CSub
        extends C {
    val sub = "sub"
    def msub() = println("CSub")
}

private object TestMain {
    def main(args: Array[String]): Unit = {
        var f: C => C = (c: C) => new C

        f = (c: CSuper) => new CSub     //1

        f = (c: CSuper) => new C        //2

        f = (c: C) => new CSub          //3

//        f = (c: CSuper) => new CSuper //4

    }
}

1, 2, 3 都是可以执行成功的
4 不能通过编译

实际上在这里var f: C => C 相当于定义var f = New Function[-C, +C]

-C 逆变相当于把 方法入参范围限制小了, 原本函数可以处理 {C、 CSuper}的内容,现在必然能处理{C}内容
+C 协变相当于把 方法出参范围扩大,原本函数返回类型可以是{CSuper、 C},由于C 是CSub父类,返回的C 必然被CSub包括,最终满足 (C)Csub,因此这里可以协变

变异标记只有在类型声明中的类型参数里才有意义,对参数化的方法没有意义,因为该标
记影响的是子类继承行为,而方法没有子类

例如 List.map 方法的简化签名:

sealed abstract class List[+A] ... { // 忽略了混入的trait
...
def map[B](f: A => B): List[B] = {...}
...
}

可变类型的逆变与协变

可变类型只允许非变异行为

类似上文Function,如果允许,默认生成Set方法应该是协变,Get方法应该是逆变
单一的逆变协变不能同时满足上述条件

Java和Scala

Java 指定变异行为时存在两个缺点。首先,库的设计者应该负责类型的编译行为并编写进
库中。但现在是库的用户承担这个负担。这导致了第二个缺点,就是 Java 程序员很容易指
定错误的变异注释,从而导致不安全的代码,就像我们刚刚讨论过的那样。

ublic class JavaArrays {
public static void main(String[] args) {
Integer[] array1 = new Integer[] {
new Integer(1), new Integer(2), new Integer(3) };
Number[] array2 = array1;
// 通过编译

array2[2] = new Double(3.14);
// 通过编译,但会在运行时抛出错误!
}
}

Scala 类型结构

Any 处于类型结构树的根部位置, Any 没有父类,但有三个子类:
• AnyVal , 价值类型和价值类的父类。
• AnyRef ,所有引用类型的父类。相当于java.lang.Object
• 通用特征(universal trait),新引入的 trait 类型,用于我们在 8.2 节讨论的特殊用途。

在 Scala 2.10 之 前, 编 译 器 对 所 有 Scala 的 引 用 类 型 混 入 了 名 为
ScalaObject 的 marker 特 征。Scala 2.11 之 后, 编 译 器 将 不 再 这 么 做, 该
trait 将会被移除。

scala类型层次结构

Noting

NothingNull 是位于类型系统底层的两个特殊类型。其中, Nothing是所有其他类型的子类,而 Null 是所有引用类型的子类。

Null 在编译器里的实现相当于以下声明:

package scala
abstract final class Null extends AnyRef

为何 Null 可以同时为 final 和 abstract 呢?该声明不允许子类派生以及创建实例,但运
行时环境提供了一个实例,就是我们熟悉和喜爱的 null。Scala 的 null 与 Java 的 null 完全相同,因为它
必须与 Java 的 null 共存于 JVM。否则,Scala 会破坏 null 的概念,引起很多潜在的 bug。

Nothing 实际上继承了 Any 。根据类型系统里的构造规则, Nothing 是所有其他类型的子类,
无论是引用类型还是值类型。换句话说, Nothing 继承了所有一切(everything),这让它的
名字听起来很奇怪。

在List中Nil通过Nothing实现,由于List[]参数协变,Nil是List子类型,实际上Nil定义为Nil[Nothing],Nothing相当Any的子类

另外Predef中 ??? 也是返回Nothing,所以可以被任何函数调用

package scala
object Predef {
...
def ??? : Nothing = throw new NotImplementedError
...
}

同时使用上限与下限

scala 允许同时限制类型上限与下限,具体使用如下

case class C[A >: Lower <: Upper](a: A)
// case class C2[A <: Upper >: Lower](a: A)
// 无法通过编译
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值