zoukankan      html  css  js  c++  java
  • Scala的型变

    在scala中Any 是任何基础数据类型的父类,但是,在scala中是不允许ArrayList[Int]的引用赋值给一个指向ArrayList[Any]的引用。像下面这样:

    object TestMain extends App {
      var arrInt:Array[Int] = Array(1, 2, 3, 4)
      var arrAny:Array[Any] = _
       arrAny = arrInt    // 这是不被允许的,编译错误
    }
    

    通常情况下,一个派生类型的集合不应该赋值给一个基类型的集合。然而,在有些情况下,我们需要放宽这一规则。这种情况下,我们可以要求scala允许在其他庆康下无效的转换。


    协变和逆变

    如上面的例子,这种情况下编译是不被允许的,可以强行编译一下,看看具体的编译错误报告:

    Error:(15, 13) type mismatch;
     	found   : Array[Int]
     	required: Array[Any]
    	Note: Int <: Any, but class Array is invariant in type T.
    	You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
       	arrAny = arrInt// 这行是不被允许的,编译错误
    

    相比java而言,scala是友好的,java对此在编译阶段是没有限制的,但是在错误会发生在运行阶段,也就是说,在java中,当把一个苹果new Apple()试图放进一个盛香蕉的篮子里Banana[]中的时候(当然,二者都是fruit的子类),编译阶段是不会报错的,但是在如果你想要吧一个命名为Apple的篮子的对象赋值给命名为Object的对象的话,同样,Java语言也是不允许这样做的:

    public class TestJavaMain {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            ArrayList<Object> list2 = list; //编译错误
        }
    }
    

    但是,在java中个,这个问题是很好解决的:ArrayList list2 = list;

    • 协变:当在希望接收一个基类实例的集合的地方,能够使用一个子类实例的集合的能力叫做协变(convariance)
    • 逆变:当在希望接收一个子类实例的集合的地方,能够使用一个超类实例的集合的能力叫做逆变(contravariance)

    默认情况下,scala是二者都不允许的,即不变

    协变

    需求:

    1. 定义两个类,分别是Dog类,Pet类
    2. Dog类继承Pet类
    3. 主类中定义一个方法workWithPets用来接收一个Pet数组的参数
    4. 要求workWithPets该方法能够接受一个Dog数组的参数,即实现协变
    class Pet(val name: String) {
      override def toString: String = name
    }
    
    class Dog(override val name: String) extends Pet(name)
    
    object TestMain {
      def main(args: Array[String]): Unit = {
        val dogs = Array(
          new Dog("Rover"),
          new Dog("Comet")
        )
        workWithPets(dogs) // 编译错误
        workWithPets_t(dogs) // 正常运行
      }
    
      // 接受pet数组的方法
      def workWithPets(Pet: Array[Pet]) = {}
    
      // 改进后
      def workWithPets_t[T <: Pet](pets: Array[T]) = {
        println("Playing with pets:" + pets.mkString(", "))
      }
    }
    

    这里使用T <: Pet符号来定义协变,这里T代表的是Pet的上界,通过指定上界,告诉scala,T 必须至少是一个Pet的派生数组。

    在通俗一点讲,协变即是:本可以接受一个父类型的参数的方法,通过协变可以使其接受一个子类的参数

    逆变

    现在,假设我们需要见个宠物的A集合复制到B集合中,那么,可以编写一个名为copy()的方法,它接受两个类型为Array[Pet]的参数。也就是说,这种场景下,B作为接受数组,它的元素的类型是源数组A中元素类型的超类,也就是这里我们需要一个下界:

    def copy[A, B >: A](fromPet: Array[A], toPet: Array[B]) = {
        println("A:" + fromPet.mkString(", ") + " B:" + toPet.mkString(", "))
      }
    

    这里限定了目标数组的参数化类型(B),将其限定为了源数组的参数话类型(A)的一个超类型。话句话说,A设定了类型B的下界--B它可以是类型A,也可以是A的超类型

    集合的型变

    如果我们需要在集合中使用派生类型,也可以通过参数化类型来完成这一操作。
    例如:假设派生类型的集合可以被看做是其基类型的集合。你可以通过将参数化类型标记为+T来完成次操作

    class MyList[+T] {
    
    }
    
    val list1 = new MyList[Int]
    var list2: MyList[Any] = new MyList[Any]
    list2 = list1
    
    

    这里 +T告诉scala允许协变,也就是说,在类型检查期间,他要求scala接受一个类型或者该类型的派生类型。因此可以将一个MyList[Int]的实例赋值给一个MyList[Any]的引用,需要记住的是,这不能是Array[Int].然而,这可以是scala库中实现的List。

    同样,可以使用参数化类型-T而不是T,我们可以要求scala为自己的类型提供逆变支持。

  • 相关阅读:
    C语言笔记
    js学习笔记
    Javascript学习笔记
    Java基础知识
    使用 StackExchange.Redis 封装属于自己的 RedisHelper
    StackExchange.Redis 使用资料
    .NET平台下Redis使用(三)【ServiceStack.Redis学习】
    .NET平台下Redis使用(二)【StackExchange.Redis学习】
    Redis 详解 (一) StackExchange.Redis Client
    .NET中使用Redis
  • 原文地址:https://www.cnblogs.com/Gxiaobai/p/14892120.html
Copyright © 2011-2022 走看看