Scala类型系统使得编译器能进行很多编译时优化和约束,从而提高运行速度和避免程序错误。通过让编译器来跟踪变量、方法和类的信息,类型系统能帮助我们避免不小心写出的错误代码。
一个类型就是编译器知道的一组信息。这可以是任何信息。信息可以由用户明确提供的,也可以是由编译器在检查其他代码时自动推断出来的。
在Scala里可以用以下两种方式定义类型:1. 定义类(class)、特质(trait)或对象(object);2. 直接用type关键字定义类型。
在Scala里,标注类型的时候可以直接用类或特质的名字来引用其类型。要引用对象的类型,需要用对象的type成员来引用其类型。
class ClassName
trait TraitName
object ObjectName
defined class ClassName defined trait TraitName defined object ObjectName
def foo(x: ClassName) = x
def bar(x: TraitName) = x
def baz(x: ObjectName.type) = x
defined function foo defined function bar defined function baz
型变是指A[T]这样的高阶类型的类型参数可以改变或变化的能力。型变的规则决定了参数化类型的顺应性。型变有三种形式:不变(Invariance)、协变(Covariance)、逆变(Contravariance)。
Java中参数化的类型在定义时并未声明继承转化关系,而是在使用该类型时,也就是声明变量时,才指定参数化类型的转化行为。
型变有以下说明:
逆变不容易理解。逆变的最好例子是一组trait FunctionN,N是介于0到22之间的数字(如scala.Function2),并且与函数所带的参数个数相对应。
Scala使用这些trait来实现匿名函数。比如函数表达式i => i+3是一个语法糖,编译器将其转换为scala.Function1的匿名子类。其实现为:
val f: Int => Int = new Function1[Int, Int] {
def apply(i: Int): Int = i + 3
}
编译器使用匿名函数的函数体来定义FunctionN抽象特质的apply方法。
tips: 当对象后面跟有参数列表时,就会调用默认的apply函数。比如,一旦定义了f,我们就通过制定参数列表的方式调用它,如f(1)实际上就是f.apply(1)。
从scala.Function2的声明可以看出:
trait Function2[-T1, -T2, +R] extends AnyRef
最后一个类型参数+R是返回类型,它是协变的。开头的两个类型参数分别是函数的第一个和第二个参数,它们是逆变的。FunctionN特质中,函数参数的类型参数都是逆变的。
你可以拿一个函数的返回值并转换为其超类。对于参数来说,你可以传入参数类型的子类。
你应该能够接受一个Any => String 类型的函数并强制转换为String => Any,但反过来不行。
// 方法的隐式型变
def foo(x: Any): String = "Hello, I received a " + x
def bar(x: String): Any = foo(x)
defined function foo defined function bar
bar("test")
res3: Any = Hello, I received a test
foo("test")
res4: String = "Hello, I received a test"
创建特质来构造函数对象
trait Function[-Arg, +Return] {
def apply(arg: Arg): Return
}
defined trait Function
val foo = new Function[Any, String] {
override def apply(arg: Any): String =
"Hello, I received a " + arg
}
foo: AnyRef with Function[Any, String] = cmd6$$user$$anonfun$1$$anon$1@6734be
val bar: Function[String, Any] = foo
bar: Function[String, Any] = cmd6$$user$$anonfun$1$$anon$1@6734be
bar("test")
res8: Any = Hello, I received a test
class CSuper {
def msuper() = println("CSuper")
}
class C extends CSuper {
def m() = println("C")
}
class CSub extends C {
def msub() = println("CSub")
}
defined class CSuper defined class C defined class CSub
定义一组匿名函数,形式为Function1[C, C],查看函数继承编译规则。
var f: C => C = (c: C) => new C
f: C => C = <function1>
f = (c: CSuper) => new CSub
f = (c: CSuper) => new C
f = (c: C) => new CSub
// 因为参数是逆变的,返回值是协变的,故参数无效
f = (c: CSub) => new CSuper
Compilation Failed
Main.scala:91: type mismatch;
found : cmd14.this.$ref$cmd9.CSuper
required: cmd10.INSTANCE.$ref$cmd9.C
f = (c: CSub) => new CSuper
^
当我们约定f的类型是C => C时,其实定义了一个契约。这样,任何有效的C类值都可以传给f,f也永远不会返回除C类值意外的任何值。
话说输入类型逆变: 如果实际函数类型为(x: CSuper) => Csub,该函数不仅可以接受任何C类值作为参数,也可以处理C的父类型实例,或其父类型的其他子类型的实例。所以,由于传入C的实例,我们永远不会传入超出f允许范围外的参数。从某种意义上说,f比我们需要的更加“宽容”。
话说输出类型协变: 当它只返回Csub时,这也是安全的。因为调用方可以处理C的实例,所以也一定可以处理CSub的实例。在这个意义上说,f比我们需要的更加“严格”。
从调用者的角度来理解,在使用函数的时候,首先看到的是函数的类型声明(上面的例子中,函数类型是C => C)。那么,函数的输入参数类型应该更加抽象(超类),因为如果函数定义中能够处理具体的,那么传入的对象是超类也能够处理;反之,如果传入的对象是更加具体的,函数体就不知道如何处理具体类的个性成分了。函数的输出类型应该更加具体(子类),不然将超出调用者的预期范围,返回值的内容不应该是所赋变量的超类,而应该是子类。
型变标记值有在类型声明中的类型参数里才有意义,对参数化方法没有意义。因为该标记影响的是子类继承行为,而方法没有子类。如果试图在方法中加入+或-标记的话,编译器将报错。
通常情况下,对于某个对象消费的值适合逆变,而对于它产出的值适合协变。 如果一个对象同时消费和产出某值,则类型应该保持不变。这通常适用于可变数据结构,比如Scala的数组Array类型就不支持型变。
对于class中的可变字段,由于存在公有的读写访问方法,将会出现可变类型变异的情况。
// 错误信息指出,我们在使用逆变类型的位置使用了协变类型A
class ContainerPlus[+A](var value: A)
Compilation Failed
Main.scala:84: covariant type A occurs in contravariant position in type A of value value_=
class ContainerPlus[+A](var value: A)
^
// 错误信息指出,我们在返回类型处使用了逆变类型A
class ContainerMinus[-A](var value: A)
Compilation Failed
Main.scala:84: contravariant type A occurs in covariant position in type => A of method value
class ContainerMinus[-A](var value: A)
^
// 这是上面ContainerPlus的显式方法声明重写,与上式等同
class ContainerPlus[+A](var a: A) {
private var _value: A = a
def value_=(newA: A): Unit = _value = newA
def value: A = _value
}
Compilation Failed Main.scala:84: covariant type A occurs in contravariant position in type A of value a_= class ContainerPlus[+A](var a: A) { ^ Main.scala:86: covariant type A occurs in contravariant position in type A of value newA def value_=(newA: A): Unit = _value = newA ^
对于getter和setter方法中的可变字段而言,它在读方法中处于协变的位置,而在写方法中又处于逆变的位置。不存在既协变又逆变的类型参数,所以对于可变字段A的唯一选择就是不变。
可变数据结构(比如Array、ArrayBuffer、ListBuffer)不支持型变,因为这样做会不安全。
val students = new Array[Student](length)
val people: Array[Person] = students //非法,假设可以
people(0) = new Person("Jason") //这样使得students(0)不再是Student了
trait Animal {
def speak
}
class Dog(var name: String) extends Animal {
def speak = println("woof")
override def toString = name
}
class SuperDog(name: String) extends Dog(name) {
def useSuperPower = println("Using my superpower!")
}
defined trait Animal defined class Dog defined class SuperDog
val fido = new Dog("Fido")
val wonderDog = new SuperDog("Wonder Dog")
val shaggy = new SuperDog("Shaggy")
fido: Dog = Fido wonderDog: SuperDog = Wonder Dog shaggy: SuperDog = Shaggy
import collection.mutable.ArrayBuffer
val dogs = ArrayBuffer[Dog]()
dogs += fido
dogs += wonderDog
import collection.mutable.ArrayBuffer dogs: collection.mutable.ArrayBuffer[Dog] = ArrayBuffer(Fido, Wonder Dog) res16_2: collection.mutable.ArrayBuffer[Dog] = ArrayBuffer(Fido, Wonder Dog) res16_3: collection.mutable.ArrayBuffer[Dog] = ArrayBuffer(Fido, Wonder Dog)
def makeDogsSpeak(dogs: ArrayBuffer[Dog]) {
dogs.foreach(_.speak)
}
defined function makeDogsSpeak
makeDogsSpeak(dogs)
woof woof
如果使用makeDogsSpeak(superDogs)将编译无法通过
val superDogs = ArrayBuffer[SuperDog]()
superDogs += shaggy
superDogs += wonderDog
makeDogsSpeak(superDogs) // ERROR: won't compile
Compilation Failed
Main.scala:126: type mismatch;
found : scala.collection.mutable.ArrayBuffer[cmd19.this.$ref$cmd14.SuperDog]
required: scala.collection.mutable.ArrayBuffer[cmd17.INSTANCE.$ref$cmd14.Dog]
Note: cmd19.this.$ref$cmd14.SuperDog <: cmd17.INSTANCE.$ref$cmd14.Dog, but class ArrayBuffer is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: cmd17.INSTANCE.$ref$cmd14.Dog`. (SLS 3.2.10)
makeDogsSpeak(superDogs)
^
像是List、Vector、Seq这样的不可变集合都支持协变
def makeDogsSpeak2(dogs: Seq[Dog]) {
dogs.foreach(_.speak)
}
defined function makeDogsSpeak2
val dogs2 = Seq(new Dog("Fido"), new Dog("Tanner"))
makeDogsSpeak2(dogs2)
woof woof
dogs2: Seq[Dog] = List(Fido, Tanner)
// this works too
val superDogs2 = Seq(new SuperDog("Wonder Dog"), new SuperDog("Scooby"))
makeDogsSpeak2(superDogs2)
woof woof
superDogs2: Seq[SuperDog] = List(Wonder Dog, Scooby)