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 将会被移除。
Noting
Nothing 和 Null 是位于类型系统底层的两个特殊类型。其中, 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)
// 无法通过编译