-
Notifications
You must be signed in to change notification settings - Fork 0
Description
什么是型变
型变在scala中灵活的分为了不变,逆变和协变
- 协变是可以用自己替换需要自己父亲的位置而是允许的,也就是当参数需要父类型参数时你可以传入子类型
- 逆变就是可以用父亲替换儿子的位置而是允许的,也就是当参数需要子类型的时候可以传入父类型
- 不变就是不能改变参数类型
首先看个代码,这段代码来自scala in depth书中
trait Function[-Arg,+Return]{
def apply(arg:Arg):Return
}
//defined trait Function
val foo = new Function[Any,String]{
override def apply(arg:Any) :String = s"Hello ,I received a $arg"
}
//foo: Function[Any,String] = $anon$1@72dda13a
val bar:Function[String,Any] = foo
//bar: Function[String,Any] = $anon$1@72dda13a
foo("test")
//res0: String = Hello ,I received a test
bar("test")
//res1: Any = Hello ,I received a test这段代码怎么来的大家可以详细去看一下scala in depth一书,在这里就不写推导过程了,只是讨论一下为什么
val bar:Function[String,Any] = foo 能编译通过,以及反过来能不能,为什么不能
1
首先我们看一下Function的定义,在参数Arg位置被定义成了逆变,这个意味着我们可以在需要某个类型的时候用他的父类型替换,也就是老子换儿子,在返回值类型被定义成了协变,这就是在需要某个类型的时候用他的子类型替换,白话就是儿子换老子
2
我们看一下foo的定义,他的参数类型是Any,返回值类型是String,所以他的apply方法接收一个Any类型的参数,返回一个String类型的结果,这就是为什么foo("test")返回的是String类型的res0
3
我们再看一下bar的定义,参数类型为String,返回值类型为Any,所以如果能调用bar,需要传入String,结果是Any,也就是bar("test")是Any的原因
4
那么为什么可以写那句val bar:Function[String,Any] = foo
这就和型变有关了,由于Function参数位置逆变,所以需要String的时候我们能用他的老子Any替换,foo类型的参数位置正好是Any,同理返回值类型协变,需要的是Any,我们传入String自然也没错,所以可以通过编译并产生结果
那么反过来还可以么
我们试试
val faz = new Function[String,Any]{
override def apply(arg:String) :Any = "Hello ,I received "+ arg
}
faz("test")
val baz :Function[Any,String] = faz
baz("test")结果想必也猜到了
type mismatch,因为baz需要的是Any的参数和String的返回值,参数位置是逆变,只能传老子或自身类型,返回值类型是协变,只能传儿子或自身类型,你给Any传儿子String自然是不允许的
那有人就说你为什么不反过来,也就是参数位置协变,返回值类型逆变
类似
trait Function[+Arg,-Return]{
def apply(arg:Arg):Return
}那么大事不好了,完全不能通过编译
因为参数必须逆变,而返回值必须协变,这也是有道理的
先说返回值如果逆变,那么也就是说你可以用老子代替儿子,可是儿子是String类型,如果老子是一个Int类型或者其他类型的Any类型引用,那么这里用Int取代替String明显会出问题,所以这是不被允许的
再说参数必须逆变,如果参数协变了,那么就是儿子可以代替老子,由于参数是Any类型,我们可以用String类型代替Any,可是实际上Any可能指向了其他儿子,那么儿子需要的是Int,你传入String自然也是不可以的