zoukankan      html  css  js  c++  java
  • scala知识体系_泛型

    本文主要讲解泛型、类型边界、协变、逆变的基础概念和应用。

    泛型定义和调用

    泛型是值定义以类型为参数的类,在scala源码中对泛型的使用相当广泛。
    一般使用字母A作为类型参数标识符,并放在方括号[]中。如果有多个类型参数,则可以依次用A,B,C等参数名称,如scala.collection.immutable包中特征Map中定义如下:

    trait Map[A, +B] extends Iterable[(A, B)]
                        with scala.collection.Map[A, B]
                        with MapLike[A, B, Map[A, B]] { self =>
    
      override def empty: Map[A, B] = Map.empty
    
      override def toMap[T, U](implicit ev: (A, B) <:< (T, U)): immutable.Map[T, U] =
        self.asInstanceOf[immutable.Map[T, U]]
    
      override def seq: Map[A, B] = this
    
      def withDefault[B1 >: B](d: A => B1): immutable.Map[A, B1] = new Map.WithDefault[A, B1](this, d)
    
    
      def withDefaultValue[B1 >: B](d: B1): immutable.Map[A, B1] = new Map.WithDefault[A, B1](this, x => d)
    
    
      override def updated [B1 >: B](key: A, value: B1): Map[A, B1]
      def + [B1 >: B](kv: (A, B1)): Map[A, B1]
    }
    

    如下我们自定义泛型类MyStack:

      class MyStack[A] {
        private var elements: List[A] = Nil
        def push(x: A) { elements = x :: elements }
        def peek: A = elements.head
        def pop(): A = {
          val currentTop = peek
          elements = elements.tail
          currentTop
        }
      }
    

    MyStack类的实现将任何类型A作为参数。这意味着
    底层列表var elements: List[A] = Nil只能存储类型的元素A。
    该程序方法push只接受类型的对象A。
    泛型类的调用和一般类的调用一致,只不过使用前先指定具体的数据类型即可,如下:

    object GenericClassDemo {
    
      class MyStack[A] {
        private var elements: List[A] = Nil
    
        def push(x: A) {
          elements = x :: elements
        }
    
        def peek: A = elements.head
    
        def pop(): A = {
          val currentTop = peek
          elements = elements.tail
          currentTop
        }
    
        def length(): Int = {
          elements.size
        }
      }
    
      abstract class Fruit {
        def name:String
      }
    
      case class Apple(name: String) extends Fruit
    
      case class Banana(name: String) extends Fruit
    
      def main(args: Array[String]) {
        val stack = new MyStack[Fruit]
        val apple = new Apple("Gala")
        val banana = new Banana("Cactus")
        stack.push(apple)
        stack.push(banana)
        println(stack.length)
      }
    }
    

    我们向类型参数为Fruit实例stack,并push一个applebanana,最后打印了堆栈的长度。
    可以看到由于applebanana均是Fruit的子类型,能pushstack中,但如果push其他类型的实例肯定会编译不通过。此外,为控制泛型类型的子类型的行为,scala采用了类型参数变型机制,主要包括协变、逆变和非变。

    参数类型的边界

    scala中,类型参数和抽象类型受到类型绑定的约束,类型边界约束了类型变量的具体值。
    讨论变型前,先看看类型的上界和下界的定义。
    上界
    B <: A 声明类型变量B是类型A的子类型。
    下界
    B >: A 声明参数类型或者抽象类型B是类型A的超类型。大多数场合下,A是类的类型参数,B是函数的类型参数。

    协变

    通过注释+A可以使泛型类的参数A变为协变类型。
    如果类F的类型参数是协变类型,对于两种类型A和B,如果B是A的子类型(B<:A),则F[B]也是F[A]的子类型(F[B]<:F[A])。
    如scala源码中scala.collection.immutable.List类的类型参数是协变类型, 定义如下:

    sealed abstract class List[+A] extends AbstractSeq[A]
                                      with LinearSeq[A]
                                      with Product
                                      with GenericTraversableTemplate[A, List]
                                      with LinearSeqOptimized[A, List[A]]
                                      with Serializable {
      def tail: List[A]
    
      override def drop(n: Int): List[A] = {
        var these = this
        var count = n
        while (!these.isEmpty && count > 0) {
          these = these.tail
          count -= 1
        }
        these
      }
    	// something else
    }
    

    在如下的代码示例中,printFruitName方法输入参数为List[Fruit],由于List是协变的,因而输入List[Fruit]的子类型List[Apple]List[Banana]也是允许的。

    object CovarianceDemo {
    
      abstract class Fruit {
        def name:String
      }
    
      case class Apple(name: String) extends Fruit
    
      case class Banana(name: String) extends Fruit
    
      def printFruitName(fruits: List[Fruit]): Unit = {
    
        fruits.foreach(fruit => println(fruit.name))
      }
    
      def main(args: Array[String]) {
        val apples: List[Apple] = List(new Apple("Gala Apple"), new Apple("Flowercow Apple"))
        val bananas: List[Banana] = List(new Banana("Banana A"), new Banana("Banana B"))
        printFruitName(apples)
        printFruitName(bananas)
      }
    }
    

    运行结果如下:

    Gala Apple
    Flowercow Apple
    Banana A
    Banana B
    

    逆变

    通过注释-A可以使泛型类的参数A变为逆变类型。
    如果类F的类型参数是逆变类型,对于两种类型A和B,如果B是A的子类型(B<:A),则F[A]F[B]的子类型(F[A]<:F[B])。
    如下的代码示例中,看我们定义Printer类,其参数为逆变类型,由于AppleFruit的子类型,我们可以知道Printer[Fruit]Printer[Apple]的子类型,这可以从函数printFruitName得到印证。我们要求的输入参数是Printer[Apple]类型,而实际输入Printer[Fruit]Printer[Apple]都能正常输出结果。

    object ContravarianceDemo {
    
      abstract class Fruit {
        def name: String
      }
    
      case class Apple(name: String) extends Fruit
    
      case class Banana(name: String) extends Fruit
    
      abstract class Printer[-A] {
        def print(a: A): Unit
      }
    
      class FruitPrinter extends Printer[Fruit] {
        override def print(fruit: Fruit): Unit = {
          println(s"fruit name is ${fruit.name}")
        }
      }
    
      class ApplePrinter extends Printer[Apple] {
        override def print(apple: Apple): Unit = {
          println(s"apple name is ${apple.name}")
        }
      }
    
      def main(args: Array[String]) {
        val apple: Apple = new Apple("Gala Apple")
        val fruitPrinter: Printer[Fruit] = new FruitPrinter
        val applePrinter: Printer[Apple] = new ApplePrinter
        def printFruitName(printer: Printer[Apple]): Unit = {
          printer.print(apple)
        }
        printFruitName(fruitPrinter)
        printFruitName(applePrinter)
      }
    
    }
    

    输出如下:

    fruit name is Gala Apple
    apple name is Gala Apple
    

    非变

    默认情况下,出于安全考虑,泛型类的参数类型是非变的,即不是协变的,也不是逆变的。
    这种情况在scala源码中应用比较广泛,比如Array类,不再赘述。

    本文参考内容如下:
    https://docs.scala-lang.org/
    https://www.scala-lang.org/api/current/
    http://twitter.github.io/scala_school/zh_cn/index.html
    http://twitter.github.io/effectivescala/index-cn.html


    关于作者
    爱编程、爱钻研、爱分享、爱生活
    关注分布式、高并发、数据挖掘
    如需捐赠,请扫码

  • 相关阅读:
    UIBezierPath 画线
    医保卡
    UITextView 监听 return key的改变
    实现 UISegmentControl 与 UIScrollView的上下级联(分别在相应的方法中加入级联代码)
    webView、scrollView、TableView,为了防止滚动时出现偏移,底部黑框问题等
    UITabBar 设置字体的颜色(选中状态/正常状态)setTitleTextAttributes
    GitLab使用方法
    Dubbo快速入门
    zookeeper的安装
    核心配置文件常用配置标签
  • 原文地址:https://www.cnblogs.com/aidodoo/p/9263479.html
Copyright © 2011-2022 走看看