In [1]:
class ClassName
trait TraitName
object ObjectName
In [2]:
def foo(x: ClassName) = x
def bar(x: TraitName) = x
def baz(x: ObjectName.type) = x
逆变不容易理解。逆变的最好例子是一组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,但反过来不行。
In [3]:
// 方法的隐式型变
def foo(x: Any): String = "Hello, I received a " + x
def bar(x: String): Any = foo(x)
In [4]:
bar("test")
In [5]:
foo("test")
创建特质来构造函数对象
In [6]:
trait Function[-Arg, +Return] {
def apply(arg: Arg): Return
}
In [7]:
val foo = new Function[Any, String] {
override def apply(arg: Any): String =
"Hello, I received a " + arg
}
In [8]:
val bar: Function[String, Any] = foo
In [9]:
bar("test")
In [10]:
class CSuper {
def msuper() = println("CSuper")
}
class C extends CSuper {
def m() = println("C")
}
class CSub extends C {
def msub() = println("CSub")
}
定义一组匿名函数,形式为Function1[C, C],查看函数继承编译规则。
In [11]:
var f: C => C = (c: C) => new C
In [12]:
f = (c: CSuper) => new CSub
In [13]:
f = (c: CSuper) => new C
In [14]:
f = (c: C) => new CSub
In [15]:
// 因为参数是逆变的,返回值是协变的,故参数无效
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)。那么,函数的输入参数类型应该更加抽象(超类),因为如果函数定义中能够处理具体的,那么传入的对象是超类也能够处理;反之,如果传入的对象是更加具体的,函数体就不知道如何处理具体类的个性成分了。函数的输出类型应该更加具体(子类),不然将超出调用者的预期范围,返回值的内容不应该是所赋变量的超类,而应该是子类。
对于class中的可变字段,由于存在公有的读写访问方法,将会出现可变类型变异的情况。
In [16]:
// 错误信息指出,我们在使用逆变类型的位置使用了协变类型A
class ContainerPlus[+A](var value: A)
In [17]:
// 错误信息指出,我们在返回类型处使用了逆变类型A
class ContainerMinus[-A](var value: A)
In [18]:
// 这是上面ContainerPlus的显式方法声明重写,与上式等同
class ContainerPlus[+A](var a: A) {
private var _value: A = a
def value_=(newA: A): Unit = _value = newA
def value: A = _value
}
对于getter和setter方法中的可变字段而言,它在读方法中处于协变的位置,而在写方法中又处于逆变的位置。不存在既协变又逆变的类型参数,所以对于可变字段A的唯一选择就是不变。
In [19]:
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!")
}
In [20]:
val fido = new Dog("Fido")
val wonderDog = new SuperDog("Wonder Dog")
val shaggy = new SuperDog("Shaggy")
In [21]:
import collection.mutable.ArrayBuffer
val dogs = ArrayBuffer[Dog]()
dogs += fido
dogs += wonderDog
In [22]:
def makeDogsSpeak(dogs: ArrayBuffer[Dog]) {
dogs.foreach(_.speak)
}
In [23]:
makeDogsSpeak(dogs)
如果使用makeDogsSpeak(superDogs)将编译无法通过
In [24]:
val superDogs = ArrayBuffer[SuperDog]()
superDogs += shaggy
superDogs += wonderDog
makeDogsSpeak(superDogs) // ERROR: won't compile
In [25]:
def makeDogsSpeak2(dogs: Seq[Dog]) {
dogs.foreach(_.speak)
}
In [26]:
val dogs2 = Seq(new Dog("Fido"), new Dog("Tanner"))
makeDogsSpeak2(dogs2)
In [27]:
// this works too
val superDogs2 = Seq(new SuperDog("Wonder Dog"), new SuperDog("Scooby"))
makeDogsSpeak2(superDogs2)