• Scala中的协变,逆变,上界,下界等


    Scala中的协变,逆变,上界,下界等

    目录 [−]

    1. Java中的协变和逆变
    2. Scala的协变
    3. Scala的逆变
    4. 下界lower bounds
    5. 上界upper bounds
    6. 综合协变,逆变,上界,下界
    7. View Bound <%
    8. Context Bound
    9. 参考文档

    Scala中的协变逆变和Java中的协变逆变不一样,看起来更复杂。 本文对Scala中的这些概念做一总结。
    首先看几个概念:

    • covariant 协变。使你能够使用比原始指定的类型的子类
    • Contravariance 逆变。使你能够使用比原始指定的类型的父类。
    • Invariance 不变。你只能使用原始指定的类型,不能协变和逆变
    • Upper bounds 上界。
    • Lower bounds 下界。

    Java中的协变和逆变

    首先我们先回顾一下Java中的协变和逆变,这样我们更容易理解Scala中的协变和逆变。
    协变

    1
    2
    3
    4
    5
    6
    class Super {
    Object getSomething(){}
    }
    class Sub extends Super {
    String getSomething() {}
    }

    Sub.getSomething()是一个协变类型,因为它的返回类型是Super.getSomething返回类型的子类。

    逆变

    1
    2
    3
    4
    5
    6
    class Super{
    void doSomething(String parameter)
    }
    class Sub extends Super{
    void doSomething(Object parameter)
    }

    Sub.getSomething()是一个逆变类型,因为它的输入参数是Super.getSomething输入参数的父类。

    泛型
    泛型中也有协变和逆变。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    List<String> aList...
    List<? extends Object> covariantList = aList;
    List<? super String> contravariantList = aList;
     
    covariantList.add("d"); //wrong
    Object a = covariantList.get(0);
     
    contravariantList.add("d"); //OK
    String b = contravariantList.get(1); //wrong
    Object c = contravariantList.get(2);

    你可以调用covariantList所有的不需要泛型参数的方法,因为泛型参数必须 extends Object, 但是编译时你不知道它确切的类型。但是你可以调用getter方法,因为返回类型总是符合Object类型。
    contravariantList正好相反,你可以调用所有的带泛型参数的方法,因为你明确的可以传入一个String的父类。但是getter方法却不行。

    Scala的协变

    首先我们需要了解的是子类型(subtyping)。一个类可以是其它类的子类(sub-)或者父类(super-)。我们可以使用数学概念(partial order)来定义:

    1
    A -> B iff A <: B

    当我们定义一个协变类型List[A+]时,List[Child]可以是List[Parent]的子类型。
    当我们定义一个逆变类型List[-A]时,List[Child]可以是List[Parent]的父类型。

    看下面的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[T](t: T) {
     
    }
    class Test extends App {
    val c:Consumer[Bird] = new Consumer[Bird](new Bird)
    val c2:Consumer[Animal] = c
    }

    c不能赋值给c2,因为Consumer定义成不变类型。

    稍微改一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[+T](t: T) {
     
    }
    class Test extends App {
    val c:Consumer[Bird] = new Consumer[Bird](new Bird)
    val c2:Consumer[Animal] = c
    }

    因为Consumer定义成协变类型的,所以Consumer[Bird]Consumer[Animal]的子类型,所以它可以被赋值给c2

    Scala的逆变

    将上面的例子改一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[-T](t: T) {
     
    }
    class Test extends App {
    val c:Consumer[Bird] = new Consumer[Bird](new Bird)
    val c2:Consumer[Hummingbird] = c
    }

    这里Consumer[-T]定义成逆变类型,所以Consumer[Bird]被看作Consumer[Hummingbird]的子类型,故c可以被赋值给c2

    下界lower bounds

    如果协变类包含带类型参数的方法时:

    1
    2
    3
    4
    5
    6
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[+T](t: T) {
    def use(t: T) = {}
    }

    编译会出错。出错信息为 "Covariant type T occurs in contravariant position in type T of value t"。
    但是如果返回结果为类型参数则没有问题。

    1
    2
    3
    4
    5
    6
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[+T](t: T) {
    def get(): T = {new T}
    }

    为了在方法的参数中使用类型参数,你需要定义下界:

    1
    2
    3
    4
    5
    6
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[+T](t: T) {
    def use[U >: T](u : U) = {println(u)}
    }

    上界upper bounds

    看一下逆变类中使用上界的例子:

    1
    2
    3
    4
    5
    6
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[-T](t: T) {
    def get[U <: T](): U = {new U}
    }

    看以看到方法的返回值是协变的位置,方法的参数是逆变的位置。
    因此协变类的类型参数可以用在方法的返回值的类型,在方法的参数类型上必须使用下界绑定 >:
    逆变类的类型参数可以用在方法的参数类型上,用做方法的返回值类型时必须使用上界绑定 <:

    综合协变,逆变,上界,下界

    一个综合例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Animal {}
    class Bird extends Animal {}
     
    class Consumer[-S,+T]() {
    def m1[U >: T](u: U): T = {new T} //协变,下界
    def m2[U <: S](s: S): U = {new U} //逆变,上界
    }
     
    class Test extends App {
    val c:Consumer[Animal,Bird] = new Consumer[Animal,Bird]()
    val c2:Consumer[Bird,Animal] = c
    c2.m1(new Animal)
    c2.m2(new Bird)
    }

    View Bound <%

    Scala还有一种视图绑定的功能,如

    1
    2
    3
    4
    5
    6
    class Bird {def sing = {}}
    class Toy {}
     
    class Consumer[T <% Bird]() {
    def use(t: T) = t.sing
    }

    或者类型参数在方法上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Bird {def sing = {}}
    class Toy {}
     
    class Consumer() {
    def use[T <% Bird](t: T) = t.sing
    }
     
    class Test extends App {
    val c = new Consumer()
    c.use(new Toy)
    }

    它要求T必须有一种隐式转换能转换成Bird,也就是 T => Bird,否则上面的代码会编译出错:

    1
    No implicit view available from Toy => Bird.

    加入一个隐式转换,编译通过。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import scala.language.implicitConversions
     
    class Bird {def sing = {}}
    class Toy {}
     
    class Consumer() {
    def use[T <% Bird](t: T) = t.sing
    }
     
    class Test extends App {
    implicit def toy2Bird(t: Toy) = new Bird
    val c = new Consumer()
    c.use(new Toy)
    }

    Context Bound

    context bound在Scala 2.8.0中引入,也被称作type class pattern
    view bound使用A <% String方式,context bound则需要参数化的类型,如Ordered[A]
    它声明了一个类型A,隐式地有一个类型B[A],语法如下:

    1
    def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]

    更清晰的一个例子:

    1
    def f[A : ClassManifest](n: Int) = new Array[A](n)

    又比如

    1
    def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
  • 相关阅读:
    Effective C++第三遍
    SQL基础教程
    hibernate 数据关联多对多
    hibernate 数据关联一对一
    hibernate 数据关联一对多
    hibernate Criteria查询
    hibernate HQL查询
    hibernate 持久化对象的生命周期
    hibernate的配置
    微博登录
  • 原文地址:https://www.cnblogs.com/yudar/p/5096434.html
Copyright © 2020-2023  润新知