Skip to content

型变之逆变与协变 #5

@cjuexuan

Description

@cjuexuan

什么是型变

型变在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 barFunction[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自然也没错,所以可以通过编译并产生结果

function1

那么反过来还可以么
我们试试

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自然是不允许的
error

那有人就说你为什么不反过来,也就是参数位置协变,返回值类型逆变
类似

trait Function[+Arg,-Return]{
  def apply(arg:Arg):Return
}

那么大事不好了,完全不能通过编译
typeerror
因为参数必须逆变,而返回值必须协变,这也是有道理的
先说返回值如果逆变,那么也就是说你可以用老子代替儿子,可是儿子是String类型,如果老子是一个Int类型或者其他类型的Any类型引用,那么这里用Int取代替String明显会出问题,所以这是不被允许的
再说参数必须逆变,如果参数协变了,那么就是儿子可以代替老子,由于参数是Any类型,我们可以用String类型代替Any,可是实际上Any可能指向了其他儿子,那么儿子需要的是Int,你传入String自然也是不可以的

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions