1.1泛型类

  • 泛型类是将类型作为参数的类。它们对集合类特别有用。
  • 定义泛类型:泛型类将类型作为方括号内的参数[]。一种惯例是使用字母A作为类型参数标识符,但是可以使用任何参数名称。
class Stack[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
}
}

  tack该类的实现将任何类型A作为参数。这意味着底层列表var elements: List[A] = Nil只能存储类型的元素A。该过程def push只接受类型的对象A(注意:elements = x :: elements重新分配elements到通过前置x当前创建的新列表elements

  • 用法:要使用泛型类,请将类型放在方括号中代替A。

val stack = new Stack[Int]
stack.push(1)
stack.push(2)
println(stack.pop)
println(stack.pop)

  结果如下:

该实例stack只能使用Int。但是,如果type参数有子类型,那么可以传入,类AppleBanana两个延伸Fruit,所以我们可以推实例applebanana到的堆叠Fruit

class Fruit
class Apple extends Fruit
class Banana extends Fruit val stack = new Stack[Fruit]
val apple = new Apple
val banana = new Banana stack.push(apple)
stack.push(banana)

注:泛型类型的子类型是*,且*不变。这意味着如果我们有一堆类型的字符,Stack[Char]那么它就不能用作类型的整数堆栈Stack[Int]。这将是不合理的,因为它将使我们能够在字符堆栈中输入真正的整数。总而言之,Stack[A]只是Stack[B]if的一个子类型,仅当它是B = A。由于这可能非常严格,因此Scala提供了一种类型参数注释机制来控制泛型类型的子类型行为。

1.2、Scala的型变

Scala在高阶类型的使用中,有三种变化,是协变逆变不变

class Foo[+A] // 协变
class Bar[-A] // 逆变
class Baz[A] // 不变 
  • 协变

  A通过使用注释,可以使泛型类的类型参数协变+A。对于某些人来说class List[+A]A协变意味着对于两种类型AB,其中A的子类B,然后List[A]的子类List[B]

abstract class Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal

Scala标准库有一个通用的不可变sealed abstract class List[+A]类,其中type参数A是协变的。这意味着List[Cat]List[Animal]的子类, List[Dog]也是List[Animal]的子类。直观地说,猫的列表和狗的列表都是动物的列表是有道理的,你应该能够用它们中的任何一个替代它们List[Animal]。如下:

object CovarianceTest extends App {
def printAnimalNames(animal: List[Animal]): Unit ={
animal.foreach{animal=>
println(animal.name)
}
}
val cat:List[Cat]=List(Cat("catone"),Cat("Tom"))
val dog:List[Dog]=List(Dog("dogone"),Dog("jurry"))
printAnimalNames(cat)
printAnimalNames(dog)
}

方法printAnimalNames将接受动物列表作为参数,并在新行上打印它们的名称。如果List[A]不是协变的,则最后两个方法调用将不会编译,这将严重限制该printAnimalNames方法的有用性。

  • 逆变

    A可以使泛型类的类型参数成为逆变-A。这会在类及其类型参数之间创建一个子类关系,该关系类似协变,但与之相反。也就是说,对于一些class Writer[-A],意味着,B是A的子类,Writer[B]Writer[A]的父类。

abstract class Printer[-A] {
def print(value: A): Unit
}

Printer[A]是一个知道如何打印出某种类型的简单类A。为特定类型定义一些子类:

class AnimalPrinter extends Printer[Animal]{
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name) }
class CatPrinter extends Printer[Cat] {
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}

  具体应用如下:

object ContravarianceTest extends App {
val myCat: Cat = Cat("Tom") def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
} val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter printMyCat(catPrinter)
printMyCat(animalPrinter)
}
    输出如下:

  • 不变

默认情况下,Scala中的泛型类是不变的。这意味着它们既不是协变也不是逆变。在以下示例的上下文中,Container类是不变的。一个Container[Cat]不是一个Container[Animal],也不是恰好相反。

class Container[A](value: A) {
private var _value: A = value
def getValue: A = _value
def setValue(value: A): Unit = {
_value = value
}
}

  看起来似乎Container[Cat]应该是 Container[Animal],但是允许可变泛型类是协变的并不安全。

在这个例子中,非常重要的Container不变量。假设Container实际上是协变的,可能会发生这样的事情:

val catContainer: Container[Cat] = new Container(Cat("Felix"))
val animalContainer: Container[Animal] = catContainer
animalContainer.setValue(Dog("Spot"))
val cat: Cat = catContainer.getValue

  

1.3、上界

在Scala中,类型参数和抽象类型成员可能受类型绑定的约束。这种类型边界限制了类型变量的具体值,并可能揭示有关这些类型成员的更多信息。

结合上类型T <: A声明类型变量T是指类型的子类型A。下面是一个示例,它演示了类的类型参数的上限类型PetContainer

abstract class Animal {
def name: String
} abstract class Pet extends Animal {} class Cat extends Pet {
override def name: String = "Cat"
} class Dog extends Pet {
override def name: String = "Dog"
} class Lion extends Animal {
override def name: String = "Lion"
} class PetContainer[P <: Pet](p: P) {
def pet: P = p
} val dogContainer = new PetContainer[Dog](new Dog)
val catContainer = new PetContainer[Cat](new Cat)

  该class PetContainer采取的类型参数P必须是的子类型PetDogCat都是子类,Pet所以可以创建一个新的PetContainer[Dog]PetContainer[Cat]。但是,如果尝试创建一个PetContainer[Lion]:

val lionContainer = new PetContainer[Lion](new Lion)

  得到以下错误:

Error:(19, 7) type arguments [lab10.Lion] do not conform to class PetContainer's type parameter bounds [P <: lab10.Pet]
val lionContainer = new PetContainer[Lion](new Lion)//Lion它不是一个Pet的子类型

  

1.4、下界(较低类型边界)

上界将类型限制为另一种类型的子类型,但下界将类型声明为另一种类型的超类型。该术语B >: A表示类型参数B或抽象类型B是指类型的超类型A。在大多数情况下,A将是类的类型参数,并且B将是方法的类型参数。如下:

trait Node[+B] {
def prepend(elem: B): Node[B]
} case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
} case class Nil[+B]() extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
}

该例实现了单链表。Nil表示空元素(即空列表)。class List Node是一个节点,它包含一个type Bhead)元素和一个对list(tail)其余部分的引用。它class Node和它的亚型是协变的,因为我们有+B

但是,此程序无法编译,因为参数elemin prepend是type B,我们声明了co变量。这不起作用,因为功能是禁忌在他们的参数类型变异和共同变种在他们的结果类型。

为了解决这个问题,我们需要翻转参数的类型的变化elemprepend。我们通过引入一个U具有B较低类型边界的新类型参数来实现此目的。

trait Node[+B] {
def prepend[U >: B](elem: U): Node[U]
} case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
} case class Nil[+B]() extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
}

  现在可以做到以下几点:

trait Bird
case class AfricanSwallow() extends Bird
case class EuropeanSwallow() extends Bird val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil())
val birdList: Node[Bird] = africanSwallowList
birdList.prepend(new EuropeanSwallow)

  

Scala实践11的更多相关文章

  1. CentOS7 安装 scala 2.11.1

    wget http://downloads.typesafe.com/scala/2.11.6/scala-2.11.6.tgz?_ga=1.61986863.2013247204.144801902 ...

  2. 【原创 Hadoop&Spark 动手实践 11】Spark Streaming 应用与动手实践

    [原创 Hadoop&Spark 动手实践 11]Spark Streaming 应用与动手实践 目标: 1. 掌握Spark Streaming的基本原理 2. 完成Spark Stream ...

  3. scala 2.11报错error: not found: type Application

    FROM: http://j-q-j.org/scala/scala-2-11-application-error.html 这两天学习scala,官网下载的最新版本2.11,书用的是<Prog ...

  4. Scala实践4

    一.数组 在Scala中,用()来访问元素,数组声明的语法格式如下 : var z:Array[String] = new Array[String](3) 或 var z = new Array[S ...

  5. 大数据系列修炼-Scala课程11

    接着昨天的list,也是学习集合的相关知识 ListBuffer.ArrayBuffer.Queue.stack相关操作 1.ListBuffer.ArrayBuffer代码实现:ListBuffer ...

  6. Scala实践8

    1.1继承类 使用extends关键字,在定义中给出子类需要而超类没有的字段和方法,或者重写超类的方法. class Person { var name = "zhangsan" ...

  7. Scala实践5

    一.Scala的层级 1.1类层级 Scala中,Any是所其他类的超类,在底端定义了一些有趣的类NULL和Nothing,是所有其他类的子类. 根类Any有两个子类:AnyVal和AnyRef.其中 ...

  8. ASP.NET-FineUI开发实践-11

    我用实例项目写了个子父页面传值,算是比较灵活的写法,可以把js提取出来写成包,然后调用,我先一步一步写,为有困难的朋友打个样. 先画个页面: 上面是个查询用的表单,底下是表格,内存分页,用到了VBox ...

  9. Socket编程实践(11) --epoll原理与封装

    常用模型的特点 Linux 下设计并发网络程序,有典型的Apache模型(Process Per Connection,PPC), TPC(Thread Per Connection)模型,以及 se ...

随机推荐

  1. 2019-10-30-C#-dotnet-core-局域网组播方法

    title author date CreateTime categories C# dotnet core 局域网组播方法 lindexi 2019-10-30 9:0:48 +0800 2019- ...

  2. iptables [match] 常用封包匹配参数

    参数 -p, --protocol 范例 iptables -A INPUT -p tcp 说明 匹配通讯协议类型是否相符,可以使用 ! 运算符进行反向匹配,例如: -p !tcp 意思是指除 tcp ...

  3. dotnet 设计规范 · 数组定义

    本文告诉大家数组定义需要知道的规范,本文翻译 docs dotnet ✓ 建议在公开的 API 使用集合而不是数组.集合可以提供更多的信息. X 不建议设置数组类型的字段为只读.虽然用户不能修改字段, ...

  4. C# double 好用的扩展

    在很多代码需要使用数学计算,在用到 double 很难直接判断一个值是 0 或者 1 ,判断两个值相等. 本文提供一个数学扩展,让大家可以简单使用到 double 判断 在开始看本文之前,希望大家是知 ...

  5. asp dotnet core 从 Frp 获取用户真实 IP 地址

    我在本地开一个服务,然后通过 Frp 让小伙伴可以在外网访问我的 API 连接,但是直接通过 RemoteIp 拿到的是本地的地址.本文告诉小伙伴如何通过 Frp 可以拿到用户的真实 IP 地址 我写 ...

  6. linux 一个写缓存例子

    我们已经几次提及 shortprint 驱动; 现在是时候真正看看. 这个模块为并口实现一个非 常简单, 面向输出的驱动; 它是足够的, 但是, 来使能文件打印. 如果你选择来测试这个 驱动, 但是, ...

  7. Vue2.0 Vue.set的使用

    原文链接: https://blog.csdn.net/qq_30455841/article/details/78666571

  8. 牛客小白月赛15A 斑羚飞渡

    链接:https://ac.nowcoder.com/acm/contest/917/A 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 131072K,其他语言262144K 64b ...

  9. vue-learning:13 - js - vue作用域概念:全局和局部

    目录 全局作用域:Vue对象 全局api 局部作用域: 实例对象vm 实例api 组件component 组件配置选项 在引入Vue文件时,就相当于拥有了一个全局Vue对象. 在var vm = ne ...

  10. bash: : Too many levels of symbolic links

    ln -s 时 bash: : Too many levels of symbolic links改为绝对路径,