scala 型变
型变是复杂类型的子类型关系与其组件类型的子类型关系的相关性。 Scala支持 泛型类 的类型参数的型变注释,允许它们是协变的,逆变的,或在没有使用注释的情况下是不变的。 在类型系统中使用型变允许我们在复杂类型之间建立直观的连接,而缺乏型变则会限制类抽象的重用性。
class Foo[+A] // A covariant class
class Bar[-A] // A contravariant class
class Baz[A] // An invariant class
协变
使用注释 +A,可以使一个泛型类的类型参数 A 成为协变。 对于某些类 class List[+A],使 A 成为协变意味着对于两种类型 A 和 B,如果 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
类型 Cat 和 Dog 都是 Animal 的子类型。 Scala 标准库有一个通用的不可变的类 sealed abstract class List[+A],其中类型参数 A 是协变的。 这意味着 List[Cat]是 List[Animal],List[Dog] 也是 List[Animal]。 直观地说,猫的列表和狗的列表都是动物的列表是合理的,你应该能够用它们中的任何一个替换 List[Animal]。
在下例中,方法 printAnimalNames 将接受动物列表作为参数,并且逐行打印出它们的名称。 如果 List[A] 不是协变的,最后两个方法调用将不能编译,这将严重限制 printAnimalNames 方法的适用性。
object CovarianceTest extends App {
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach { animal =>
println(animal.name)
}
}
val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
// Whiskers
// Tom
printAnimalNames(dogs)
// Fido
// Rex
}
逆变
通过使用注释 -A,可以使一个泛型类的类型参数 A 成为逆变。 与协变类似,这会在类及其类型参数之间创建一个子类型关系,但其作用与协变完全相反。 也就是说,对于某个类 class Writer[-A] ,使 A 逆变意味着对于两种类型 A 和 B,如果 A 是 B 的子类型,那么 Writer[B] 是 Writer[A] 的子类型。
考虑在下例中使用上面定义的类 Cat,Dog 和 Animal :
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)
}
如果 Printer[Cat] 知道如何在控制台打印出任意 Cat,并且 Printer[Animal] 知道如何在控制台打印出任意 Animal,那么 Printer[Animal] 也应该知道如何打印出 Cat 就是合理的。 反向关系不适用,因为 Printer[Cat] 并不知道如何在控制台打印出任意 Animal。 因此,如果我们愿意,我们应该能够用 Printer[Animal] 替换 Printer[Cat],而使 Printer[A] 逆变允许我们做到这一点。
object ContravarianceTest extends App {
val myCat: Cat = Cat("Boots")
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)
}
这个程序的输出如下:
The cat's name is: Boots
The animal's name is: Boots
不变
默认情况下,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 // 糟糕,我们最终会将一只狗作为值分配给一只猫
幸运的是,编译器在此之前就会阻止我们。
其他例子
另一个可以帮助理解型变的例子是 Scala 标准库中的 trait Function1[-T, +R]。 Function1 表示具有一个参数的函数,其中第一个类型参数 T 表示参数类型,第二个类型参数 R 表示返回类型。 Function1 在其参数类型上是逆变的,并且在其返回类型上是协变的。 对于这个例子,我们将使用文字符号 A => B 来表示 Function1[A, B]。
假设前面使用过的类似 Cat,Dog,Animal 的继承关系,加上以下内容:
abstract class SmallAnimal extends Animal
case class Mouse(name: String) extends SmallAnimal
假设我们正在处理接受动物类型的函数,并返回他们的食物类型。 如果我们想要一个 Cat => SmallAnimal(因为猫吃小动物),但是给它一个 Animal => Mouse,我们的程序仍然可以工作。 直观地看,一个 Animal => Mouse 的函数仍然会接受一个 Cat 作为参数,因为 Cat 即是一个 Animal,并且这个函数返回一个 Mouse,也是一个 SmallAnimal。 既然我们可以安全地,隐式地用前者代替后者,我们可以说 Animal => Mouse 是 Cat => SmallAnimal 的子类型。
与其他语言的比较
某些与 Scala 类似的语言以不同的方式支持型变。 例如,Scala 中的型变注释与 C# 中的非常相似,在定义类抽象时添加型变注释(声明点型变)。 但是在Java中,当类抽象被使用时(使用点型变),才会给出型变注释。
scala 型变的更多相关文章
- Kotlin 型变 + 星号投影(扯蛋)
Kotlin中的型变: 1. in,顾名思义,就是只能作为传入参数的参数类型 2.out, ..............,就是只能作为返回类型参数的参数类型 星号投影: 我们引用官网的吧-- For ...
- Scala 变长参数
如果Scala定义变长参数 def sum(i Int*), 那么调用sum时,可以直接输入sum(1,2,3,4,5) 但是不可以sum(1 to 5) 必须要将1 to 5 强制为seq sum( ...
- scala 学习: 逆变和协变
scala 逆变和协变的概念网上有很多解释, 总结一句话就是 参数是逆变的或者不变的,返回值是协变的或者不变的. 但是为什么是这样的? 协变: 当s 是A的子类, 那么func(s) 是func(A) ...
- Beginning Scala study note(8) Scala Type System
1. Unified Type System Scala has a unified type system, enclosed by the type Any at the top of the h ...
- [Scala] 快学Scala A2L2
集合 13.1 集合的三大类 所有的集合都扩展Iterable特质.集合的三大集合为Seq, Set, Map Seq是一个有先后次序的值的序列,比如数组或列表.IndexSeq允许我们通过整型下表快 ...
- Scala函数式编程(四)函数式的数据结构 上
这次来说说函数式的数据结构是什么样子的,本章会先用一个list来举例子说明,最后给出一个Tree数据结构的练习,放在公众号里面,练习里面给出了基本的结构,但代码是空缺的需要补上,此外还有预留的test ...
- Scala Type Parameters 2
类型关系 Scala 支持在泛型类上使用型变注释,用来表示复杂类型.组合类型的子类型关系间的相关性 协变 +T,变化方向相同,通常用在生产 假设 A extends T, 对于 Clazz[+T],则 ...
- Scala学习十七——类型参数
一.本章要点 类.特质.方法和函数都可以有类型参数 将类型参数放置在名称之后,以方括号括起来 类型界定的语法为T<:UpperBound.T>:LowerBound.T<%ViewB ...
- Scala学习十五——注解
一.本章要点 可以为类.方法.字段.局部变量.参数.表达式.类型参数以及各种类型定义添加注解 对于表达式和类型,注解跟在被注解的条目之后 注解的形式有@Annotation.@Annotation(v ...
随机推荐
- 30分钟用 Laravel 实现一个博客
介绍 Laravel 是一款 MVC架构. 目前最流行的 PHP框架. Laravel的优点在于: 丰富的composer类库支持, 优雅的代码, 未来的主流框架(目前市场占有率最高的框架) Lara ...
- 第十节:Asp.Net Core 配置详解和选项模式
一. 各种文件的读取 1.说明 在.Net Core中,各种配置文件的读取都需要依赖[Microsoft.Extensions.Configuration]程序集,当然在Asp.Net Core中已经 ...
- sitecore-多变量测试与A / B测试概念论述
sitecore-多变量检验和A / B检验的区别 A / B测试和多变量测试有什么区别?让我们来看看这些测试方法的方法,常见用途,优点和局限性. sitecore中A / B测试 A / B测试(您 ...
- bootstrap使用笔记
本文翻译自[http://getbootstrap.com/2.3.2/scaffolding.html#gridSystem] 1.需要在H5的文档声明中使用: 2.如下一个简单的两列布局,先添加一 ...
- FusionInsight大数据开发---Oozie应用开发
Oozie应用开发 要求: 了解Oozie应用开发适用场景 掌握Oozie应用开发 熟悉并使用Oozie常用API Oozie简介 Oozie是一个Hadoop作业的工作流调度管理系统 Oozie工作 ...
- EasyUI datagrid-export 将datagrid的数据导出至Excel-解决科学计数法
通过EasyUI datagrid-export.js 将datagrid的数据导出至Excel的时候,如果有类似身份证一样很长的数字,需要在后台返回数据的时候在数字前增加一个 “ ”,将数字转为字符 ...
- Java IO---序列化和反序列化
一.序列化和反序列化介绍 什么是序列化和反序列化? 序列化就是将对象转换为字节序列的过程. 反序列化就是将字节序列恢复为对象的过程. 序列化的用途在哪? 通常情况下,序列化有两个用途: 将对象 ...
- 2019 开创java面试笔试题 (含面试题解析)
本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.开创等公司offer,岗位是Java后端开发,因为发展原因最终选择去了开创,入职一年时间了,也成为了面试官,之 ...
- Java自学-日期 日期格式化
Java中使用SimpleDateFormat 进行日期格式化类 SimpleDateFormat 日期格式化类 示例 1 : 日期转字符串 y 代表年 M 代表月 d 代表日 H 代表24进制的小时 ...
- MongoDB常用数据库命令第二集
=======================基础命令======================= mongo 进入数据库操作界面db 查看当前使用的数据库show dbs 查看当前已经被创建出来的 ...