前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Scala:样例类、模式匹配、Option、偏函数、泛型(三)

Scala:样例类、模式匹配、Option、偏函数、泛型(三)

作者头像
Maynor
发布2021-12-07 10:36:52
2.2K0
发布2021-12-07 10:36:52
举报

Scala:样例类、模式匹配、Option、偏函数、泛型

课程目标

  • 掌握样例类的使用
  • 掌握模式匹配的使用

1. 样例类

样例类是一种特殊类,它可以用来快速定义一个用于保存数据的类(类似于Java POJO类),在后续要学习并发编程和spark、flink这些框架也都会经常使用它。

1.1 定义样例类

语法格式

代码语言:javascript
复制
case class 样例类名([var/val] 成员变量名1:类型1, 成员变量名2:类型2, 成员变量名3:类型3)
  • 如果要实现某个成员变量可以被修改,可以添加var
  • 默认为val,可以省略

1.2 定义一个样例类

需求

  • 定义一个Person样例类,包含姓名和年龄成员变量
  • 创建样例类的对象实例(“张三”、20),并打印它

参考代码

代码语言:javascript
复制
object _01CaseClassDemo {
  case class Person(name:String, age:Int)

  def main(args: Array[String]): Unit = {
    val zhangsan = Person("张三", 20)

    println(zhangsan)
  }
}

1.3 可变成员变量

需求

  • 定义一个Person样例类,包含姓名和年龄成员变量
  • 创建样例类的对象实例(“张三”、20)
  • 修改张三的年龄为23岁,并打印

参考代码

代码语言:javascript
复制
object _02CaseClassDemo {
  case class Person(var name:String, var age:Int)

  def main(args: Array[String]): Unit = {
    val zhangsan = Person("张三", 20)

    zhangsan.age = 23

    println(zhangsan)
  }
}

1.4 样例类的方法

当我们定义一个样例类,编译器自动帮助我们实现了以下几个有用的方法:

  • apply方法
  • toString方法
  • equals方法
  • hashCode方法
  • copy方法
1.4.1 apply方法

apply方法可以让我们快速地使用类名来创建对象。参考以下代码:

代码语言:javascript
复制
case class CasePerson(name:String, age:Int)

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    val lisi = CasePerson("李四", 21)
    println(lisi.toString)
  }
}
1.4.2 toString方法

toString返回样例类名称(成员变量1, 成员变量2, 成员变量3…),我们可以更方面查看样例类的成员

代码语言:javascript
复制
case class CasePerson(name:String, age:Int)

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    val lisi = CasePerson("李四", 21)
    println(lisi.toString)
    // 输出:CasePerson(李四,21)
  }
}
1.4.3 equals方法

样例类自动实现了equals方法,可以直接使用==比较两个样例类是否相等,即所有的成员变量是否相等

示例

  • 创建一个样例类Person,包含姓名、年龄
  • 创建名字年龄分别为"李四", 21的两个对象
  • 比较它们是否相等
代码语言:javascript
复制
val lisi1 = CasePerson("李四", 21)
val lisi2 = CasePerson("李四", 21)
println(lisi1 == lisi2)
// 输出:true
1.4.4 hashCode方法

样例类自动实现了hashCode方法,如果所有成员变量的值相同,则hash值相同,只要有一个不一样,则hash值不一样。

示例

  • 创建名字年龄分别为"李四", 21的对象
  • 再创建一个名字年龄分别为"李四", 22的对象
  • 分别打印这两个对象的哈希值
代码语言:javascript
复制
val lisi1 = CasePerson("李四", 21)
val lisi2 = CasePerson("李四", 22)

println(lisi1.hashCode())
println(lisi2.hashCode())
1.4.5 copy方法

样例类实现了copy方法,可以快速创建一个相同的实例对象,可以使用带名参数指定给成员进行重新赋值

示例

  • 创建名字年龄分别为"李四", 21的对象
  • 通过copy拷贝,名字为"王五"的对象
代码语言:javascript
复制
val lisi1 = CasePerson("李四", 21)

val wangwu = lisi1.copy(name="王五")
println(wangwu)

2. 样例对象

它主要用在两个地方:

  1. 定义枚举
  2. 作为没有任何参数的消息传递(后面Akka编程会讲到)

2.1 定义

使用case object可以创建样例对象。样例对象是单例的,而且它没有主构造器

语法格式

代码语言:javascript
复制
case object 样例对象名

3. 模式匹配

scala中有一个非常强大的模式匹配机制,可以应用在很多场景:

  • switch语句
  • 类型查询
  • 使用模式匹配快速获取数据

3.1 简单模式匹配

在Java中,有switch关键字,可以简化if条件判断语句。在scala中,可以使用match表达式替代。

语法格式

代码语言:javascript
复制
变量 match {
    case "常量1" => 表达式1
    case "常量2" => 表达式2
    case "常量3" => 表达式3
    case _ => 表达式4		// 默认配
}

示例

需求说明

  1. 从控制台输入一个单词(使用StdIn.readLine方法)
  2. 判断该单词是否能够匹配以下单词,如果能匹配,返回一句话
  3. 打印这句话

单词

返回

hadoop

大数据分布式存储和计算框架

zookeeper

大数据分布式协调服务框架

spark

大数据分布式内存计算框架

未匹配

未匹配

参考代码

代码语言:javascript
复制
println("请输出一个词:")
// StdIn.readLine表示从控制台读取一行文本
val name = StdIn.readLine()

val result = name match {
    case "hadoop" => "大数据分布式存储和计算框架"
    case "zookeeper" => "大数据分布式协调服务框架"
    case "spark" => "大数据分布式内存计算框架"
    case _ => "未匹配"
}

println(result)

3.2 匹配类型

除了像Java中的switch匹配数据之外,match表达式还可以进行类型匹配。如果我们要根据不同的数据类型,来执行不同的逻辑,也可以使用match表达式来实现。

定义

语法格式

代码语言:javascript
复制
变量 match {
    case 类型1变量名: 类型1 => 表达式1
    case 类型2变量名: 类型2 => 表达式2
    case 类型3变量名: 类型3 => 表达式3
    ...
    case _ => 表达式4
}

示例

需求说明

  • 定义一个变量为Any类型,然后分别给其赋值为"hadoop"、1、1.0
  • 定义模式匹配,然后分别打印类型的名称

参考代码

代码语言:javascript
复制
val a:Any = "hadoop"

val result = a match {
    case _:String => "String"
    case _:Int => "Int"
    case _:Double => "Double"
}

println(result)

[!NOTE] 如果case表达式中无需使用到匹配到的变量,可以使用下划线代代替

3.3 守卫

在Java中,只能简单地添加多个case标签,例如:要匹配0-7,就需要写出来8个case语句。例如:

代码语言:javascript
复制
int a = 0;
switch(a) {
    case 0: a += 1;
    case 1: a += 1;
    case 2: a += 1;
    case 3: a += 1;
    case 4: a += 2;
    case 5: a += 2;
    case 6: a += 2;
    case 7: a += 2;
    default: a = 0;
}

在scala中,可以使用守卫来简化上述代码——也就是在case语句中添加if条件判断

示例

需求说明

  • 从控制台读入一个数字a(使用StdIn.readInt)
  • 如果 a >= 0 而且 a <= 3,打印[0-3]
  • 如果 a >= 4 而且 a <= 8,打印[3,8]
  • 否则,打印未匹配

参考代码

代码语言:javascript
复制
val a = StdIn.readInt()

a match {
    case _ if a >= 0 && a <= 3 => println("[0-3]")
    case _ if a >= 4 && a <= 8 => println("[3-8]")
    case _ => println("未匹配")
}

3.4 匹配样例类

scala可以使用模式匹配来匹配样例类,从而可以快速获取样例类中的成员数据。后续,我们在开发Akka案例时,还会用到。

示例

需求说明

  • 创建两个样例类Customer、Order
    • Customer包含姓名、年龄字段
    • Order包含id字段
  • 分别定义两个案例类的对象,并指定为Any类型
  • 使用模式匹配这两个对象,并分别打印它们的成员变量值

参考代码

代码语言:javascript
复制
// 1. 创建两个样例类
case class Person(name:String, age:Int)
case class Order(id:String)

def main(args: Array[String]): Unit = {
    // 2. 创建样例类对象,并赋值为Any类型
    val zhangsan:Any = Person("张三", 20)
    val order1:Any = Order("001")

    // 3. 使用match...case表达式来进行模式匹配
    // 获取样例类中成员变量
    order1 match {
        case Person(name, age) => println(s"姓名:${name} 年龄:${age}")
        case Order(id1) => println(s"ID为:${id1}")
        case _ => println("未匹配")
    }
}

3.5 匹配集合

scala中的模式匹配,还能用来匹配集合。

3.6 匹配数组

示例说明

参考代码

代码语言:javascript
复制
val arr = Array(1, 3, 5)
arr match {
    case Array(1, x, y) => println(x + " " + y)
    case Array(0) => println("only 0")
    case Array(0, _*) => println("0 ...")
    case _ => println("something else")
}

3.7 匹配列表

示例说明

参考代码

代码语言:javascript
复制
val list = List(0, 1, 2)

list match {
    case 0 :: Nil => println("只有0的列表")
    case 0 :: tail => println("0开头的列表")
    case x :: y :: Nil => println(s"只有另两个元素${x}, ${y}的列表")
    case _ => println("未匹配")
}

3.8 匹配元组

示例说明

参考代码

代码语言:javascript
复制
val tuple = (2, 2, 5)

tuple match {
    case (1, x, y) => println(s"三个元素,1开头的元组:1, ${x}, ${y}")
    case (x, y, 5) => println(s"三个元素,5结尾的元组:${x}, ${y}, 5")
    case _ => println("未匹配")
}

3.9 变量声明中的模式匹配

在定义变量的时候,可以使用模式匹配快速获取数据

3.9.1 示例 | 获取数组中的元素

需求说明

  • 生成包含0-10数字的数组,使用模式匹配分别获取第二个、第三个、第四个元素

参考代码

代码语言:javascript
复制
val array = (1 to 10).toArray
val Array(_, x, y, z, _*) = array

println(x, y, z)
3.9.2 示例 | 获取List中的数据

需求说明

  • 生成包含0-10数字的列表,使用模式匹配分别获取第一个、第二个元素

参考代码

代码语言:javascript
复制
val list = (1 to 10).toList
val x :: y :: tail = list

println(x, y)

4. Option类型

使用Option类型,可以用来有效避免空引用(null)异常。也就是说,将来我们返回某些数据时,可以返回一个Option类型来替代。

定义

scala中,Option类型来表示可选值。这种类型的数据有两种形式:

  • Some(x):表示实际的值
1552556858868
1552556858868
  • None:表示没有值
1552556882910
1552556882910
  • 使用getOrElse方法,当值为None是可以指定一个默认值

示例一

示例说明

  • 定义一个两个数相除的方法,使用Option类型来封装结果
  • 然后使用模式匹配来打印结果
    • 不是除零,打印结果
    • 除零打印异常错误

参考代码

代码语言:javascript
复制
  /**
    * 定义除法操作
    * @param a 参数1
    * @param b 参数2
    * @return Option包装Double类型
    */
  def dvi(a:Double, b:Double):Option[Double] = {
    if(b != 0) {
      Some(a / b)
    }
    else {
      None
    }
  }

  def main(args: Array[String]): Unit = {
    val result1 = dvi(1.0, 5)

    result1 match {
      case Some(x) => println(x)
      case None => println("除零异常")
    }
  }

示例二

示例说明

  • 重写上述案例,使用getOrElse方法,当除零时,或者默认值为0

参考代码

代码语言:javascript
复制
def dvi(a:Double, b:Double) = {
    if(b != 0) {
        Some(a / b)
    }
    else {
        None
    }
}

def main(args: Array[String]): Unit = {
    val result = dvi(1, 0).getOrElse(0)

    println(result)
}

5. 偏函数

偏函数可以提供了简洁的语法,可以简化函数的定义。配合集合的函数式编程,可以让代码更加优雅。

定义

  • 偏函数被包在花括号内没有match的一组case语句是一个偏函数
  • 偏函数是PartialFunction[A, B]的一个实例
    • A代表输入参数类型
    • B代表返回结果类型

示例一

示例说明

定义一个偏函数,根据以下方式返回

输入

返回值

1

2

3

其他

其他

参考代码

代码语言:javascript
复制
// func1是一个输入参数为Int类型,返回值为String类型的偏函数
val func1: PartialFunction[Int, String] = {
    case 1 => "一"
    case 2 => "二"
    case 3 => "三"
    case _ => "其他"
}

println(func1(2))

示例二

示例说明

  • 定义一个列表,包含1-10的数字
  • 请将1-3的数字都转换为[1-3]
  • 请将4-8的数字都转换为[4-8]
  • 将其他的数字转换为(8-*]

参考代码

代码语言:javascript
复制
val list = (1 to 10).toList

val list2 = list.map{
    case x if x >= 1 && x <= 3 => "[1-3]"
    case x if x >= 4 && x <= 8 => "[4-8]"
    case x if x > 8 => "(8-*]"
}

println(list2)

6. 正则表达式

在scala中,可以很方便地使用正则表达式来匹配数据。

定义

Regex类

findAllMatchIn方法

  • 使用findAllMatchIn方法可以获取到所有正则匹配到的字符串

示例一

示例说明

  • 定义一个正则表达式,来匹配邮箱是否合法
  • 合法邮箱测试:qq12344@163.com
  • 不合法邮箱测试:qq12344@.com

参考代码

代码语言:javascript
复制
val r = """.+@.+\..+""".r

val eml1 = "qq12344@163.com"
val eml2 = "qq12344@.com"

if(r.findAllMatchIn(eml1).size > 0) {
    println(eml1 + "邮箱合法")
}
else {
    println(eml1 + "邮箱不合法")
}

if(r.findAllMatchIn(eml2).size > 0) {
    println(eml2 + "邮箱合法")
}
else {
    println(eml2 + "邮箱不合法")
}

示例二

示例说明

找出以下列表中的所有不合法的邮箱

代码语言:javascript
复制
"38123845@qq.com", "a1da88123f@gmail.com", "zhansan@163.com", "123afadff.com"

参考代码

代码语言:javascript
复制
val emlList =
List("38123845@qq.com", "a1da88123f@gmail.com", "zhansan@163.com", "123afadff.com")

val regex = """.+@.+\..+""".r

val invalidEmlList = emlList.filter {
    x =>
    if (regex.findAllMatchIn(x).size < 1) true else false
}

println(invalidEmlList)

示例三

示例说明

参考代码

代码语言:javascript
复制
// 使用括号表示一个分组
val regex = """.+@(.+)\..+""".r

val emlList =
List("38123845@qq.com", "a1da88123f@gmail.com", "zhansan@163.com", "123afadff.com")

val emlCmpList = emlList.map {
    case x@regex(company) => s"${x} => ${company}"
    case x => x + "=>未知"
}

println(emlCmpList)

7. 异常处理

来看看下面一段代码。

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
   val i = 10 / 0
    
    println("你好!")
  }

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ForDemo$.main(ForDemo.scala:3)
	at ForDemo.main(ForDemo.scala)

执行程序,可以看到scala抛出了异常,而且没有打印出来"你好"。说明程序出现错误后就终止了。

那怎么解决该问题呢?

在scala中,可以使用异常处理来解决这个问题

7.1 捕获异常

语法格式

代码语言:javascript
复制
try {
    // 代码
}
catch {
    case ex:异常类型1 => // 代码
    case ex:异常类型2 => // 代码
}
finally {
    // 代码
}
  • try中的代码是我们编写的业务处理代码
  • 在catch中表示当出现某个异常时,需要执行的代码
  • 在finally中,是不管是否出现异常都会执行的代码

示例

示例说明

  • 使用try…catch来捕获除零异常

参考代码

代码语言:javascript
复制
try {
    val i = 10 / 0

    println("你好!")
} catch {
    case ex: Exception => println(ex.getMessage)
} 

7.2 抛出异常

我们也可以在一个方法中,抛出异常。语法格式和Java类似,使用throw new Exception...

抛出异常

示例说明

  • 在main方法中抛出一个异常

参考代码

代码语言:javascript
复制
  def main(args: Array[String]): Unit = {
    throw new Exception("这是一个异常")
  }

Exception in thread "main" java.lang.Exception: 这是一个异常
	at ForDemo$.main(ForDemo.scala:3)
	at ForDemo.main(ForDemo.scala)
  • scala不需要在方法上声明要抛出的异常,它已经解决了再Java中被认为是设计失败的检查型异常。

下面是Java代码

代码语言:javascript
复制
public static void main(String[] args) throws Exception {
    throw new Exception("这是一个异常");
}

8. 提取器(Extractor)

我们之前已经使用过scala中非常强大的模式匹配功能了,通过模式匹配,我们可以快速匹配样例类中的成员变量。例如:

代码语言:javascript
复制
// 1. 创建两个样例类
case class Person(name:String, age:Int)
case class Order(id:String)

def main(args: Array[String]): Unit = {
    // 2. 创建样例类对象,并赋值为Any类型
    val zhangsan:Any = Person("张三", 20)
    val order1:Any = Order("001")

    // 3. 使用match...case表达式来进行模式匹配
    // 获取样例类中成员变量
    order1 match {
        case Person(name, age) => println(s"姓名:${name} 年龄:${age}")
        case Order(id1) => println(s"ID为:${id1}")
        case _ => println("未匹配")
    }
}

那是不是所有的类都可以进行这样的模式匹配呢?答案是:

不可以的。要支持模式匹配,必须要实现一个提取器

[!NOTE] 样例类自动实现了apply、unapply方法

8.1 定义提取器

之前我们学习过了,实现一个类的伴生对象中的apply方法,可以用类名来快速构建一个对象。伴生对象中,还有一个unapply方法。与apply相反,unapply是将该类的对象,拆解为一个个的元素。

1552639637165
1552639637165
1552639674932
1552639674932

要实现一个类的提取器,只需要在该类的伴生对象中实现一个unapply方法即可。

语法格式

代码语言:javascript
复制
def unapply(stu:Student):Option[(类型1, 类型2, 类型3...)] = {
    if(stu != null) {
        Some((变量1, 变量2, 变量3...))
    }
    else {
        None
    }
}

示例

示例说明

  • 创建一个Student类,包含姓名年龄两个字段
  • 实现一个类的解构器,并使用match表达式进行模式匹配,提取类中的字段。

参考代码

代码语言:javascript
复制
class Student(var name:String, var age:Int)

object Student {
    def apply(name:String, age:Int) = {
        new Student(name, age)
    }

    def unapply(student:Student) = {
        val tuple = (student.name, student.age)

        Some(tuple)
    }
}

def main(args: Array[String]): Unit = {
    val zhangsan = Student("张三", 20)

    zhangsan match {
        case Student(name, age) => println(s"${name} => ${age}")
    }
}

9. 泛型

scala和Java一样,类和特质、方法都可以支持泛型。我们在学习集合的时候,一般都会涉及到泛型。

代码语言:javascript
复制
scala> val list1:List[String] = List("1", "2", "3")
list1: List[String] = List(1, 2, 3)

那如何自己定义泛型呢?

9.1 定义一个泛型方法

在scala中,使用方括号来定义类型参数。

语法格式

代码语言:javascript
复制
def 方法名[泛型名称](..) = {
    //...
}

示例

示例说明

  • 用一个方法来获取任意类型数组的中间的元素
    • 不考虑泛型直接实现(基于Array[Int]实现)
    • 加入泛型支持

参考代码

不考虑泛型的实现

代码语言:javascript
复制
  def getMiddle(arr:Array[Int]) = arr(arr.length / 2)

  def main(args: Array[String]): Unit = {
    val arr1 = Array(1,2,3,4,5)

    println(getMiddle(arr1))
  }

加入泛型支持

代码语言:javascript
复制
def getMiddleElement[T](array:Array[T]) =
array(array.length / 2)

def main(args: Array[String]): Unit = {
    println(getMiddleElement(Array(1, 2, 3, 4, 5)))
    println(getMiddleElement(Array("a", "b", "c", "d", "e")))
}

9.2 泛型类

scala的类也可以定义泛型。接下来,我们来学习如何定义scala的泛型类

定义

语法格式

代码语言:javascript
复制
class 类[T](val 变量名: T)
  • 定义一个泛型类,直接在类名后面加上方括号,指定要使用的泛型参数
  • 指定类对应的泛型参数后,就使用这些类型参数来定义变量了

示例

示例说明

  • 实现一个Pair泛型类
  • Pair类包含两个字段,而且两个字段的类型不固定
  • 创建不同类型泛型类对象,并打印

参考代码

代码语言:javascript
复制
case class Pair[T](var a:T, var b:T)

def main(args: Array[String]): Unit = {
    val pairList = List(
        Pair("Hadoop", "Storm"),
        Pair("Hadoop", 2008),
        Pair(1.0, 2.0),
        Pair("Hadoop", Some(1.9))
    )

    println(pairList)
}

9.3 上下界

需求:

我们在定义方法/类的泛型时,限定必须从哪个类继承、或者必须是哪个类的父类。此时,就需要使用到上下界。

9.3.1 上界定义

使用<: 类型名表示给类型添加一个上界,表示泛型参数必须要从该类(或本身)继承

语法格式

代码语言:javascript
复制
[T <: 类型]

示例

示例说明

  • 定义一个Person类
  • 定义一个Student类,继承Person类
  • 定义一个demo泛型方法,该方法接收一个Array参数,
  • 限定demo方法的Array元素类型只能是Person或者Person的子类
  • 测试调用demo,传入不同元素类型的Array

参考代码

代码语言:javascript
复制
class Person
class Student extends Person

def demo[T <: Person](a:Array[T]) = println(a)

def main(args: Array[String]): Unit = {
    demo(Array(new Person))
    demo(Array(new Student))
    // 编译出错,必须是Person的子类
    // demo(Array("hadoop"))
}
9.3.2 下界

上界是要求必须是某个类的子类,或者必须从某个类继承,而下界是必须是某个类的父类(或本身)

语法格式

代码语言:javascript
复制
[T >: 类型]

[!NOTE] 如果类既有上界、又有下界。下界写在前面,上界写在后面

示例

示例说明

  • 定义一个Person类
  • 定义一个Policeman类,继承Person类
  • 定义一个Superman类,继承Policeman类
  • 定义一个demo泛型方法,该方法接收一个Array参数,
  • 限定demo方法的Array元素类型只能是Person、Policeman
  • 测试调用demo,传入不同元素类型的Array

参考代码

代码语言:javascript
复制
class Person
class Policeman extends Person
class Superman extends Policeman

def demo[T >: Policeman](array:Array[T]) = println(array)

def main(args: Array[String]): Unit = {
    demo(Array(new Person))
    demo(Array(new Policeman))
    // 编译出错:Superman是Policeman的子类
    // demo(Array(new Superman))
}

9.4 协变、逆变、非变

spark的源代码中大量使用到了协变、逆变、非变,学习该知识点对我们将来阅读spark源代码很有帮助。

来看一个类型转换的问题:

代码语言:javascript
复制
class Pair[T]

object Pair {
  def main(args: Array[String]): Unit = {
    val p1 = Pair("hello")
    // 编译报错,无法将p1转换为p2
    val p2:Pair[AnyRef] = p1

    println(p2)
  }
}

如何让带有泛型的类支持类型转换呢?

9.4.1 非变

语法格式

代码语言:javascript
复制
class Pair[T]{}
  • 默认泛型类是非变的
  • 类型B是A的子类型,Pair[A]和Pair[B]没有任何从属关系
  • Java是一样的
1558064807949
1558064807949
9.4.2 协变

语法格式

代码语言:javascript
复制
class Pair[+T]
  • 类型B是A的子类型,Pair[B]可以认为是Pair[A]的子类型
  • 参数化类型的方向和类型的方向是一致的。
9.4.3 逆变

语法格式

代码语言:javascript
复制
class Pair[-T]
  • 类型B是A的子类型,Pair[A]反过来可以认为是Pair[B]的子类型
  • 参数化类型的方向和类型的方向是相反的

示例

示例说明

  • 定义一个Super类、以及一个Sub类继承自Super类
  • 使用协变、逆变、非变分别定义三个泛型类
  • 分别创建泛型类来演示协变、逆变、非变

参考代码

代码语言:javascript
复制
class Super
class Sub extends Super

class Temp1[T]
class Temp2[+T]
class Temp3[-T]

def main(args: Array[String]): Unit = {
    val a:Temp1[Sub] = new Temp1[Sub]
    // 编译报错
    // 非变
    //val b:Temp1[Super] = a

    // 协变
    val c: Temp2[Sub] = new Temp2[Sub]
    val d: Temp2[Super] = c

    // 逆变
    val e: Temp3[Super] = new Temp3[Super]
    val f: Temp3[Sub] = e
}

使用到了协变、逆变、非变,学习该知识点对我们将来阅读spark源代码很有帮助。

来看一个类型转换的问题:

代码语言:javascript
复制
class Pair[T]

object Pair {
  def main(args: Array[String]): Unit = {
    val p1 = Pair("hello")
    // 编译报错,无法将p1转换为p2
    val p2:Pair[AnyRef] = p1

    println(p2)
  }
}

如何让带有泛型的类支持类型转换呢?

9.4.1 非变

语法格式

代码语言:javascript
复制
class Pair[T]{}
  • 默认泛型类是非变的
  • 类型B是A的子类型,Pair[A]和Pair[B]没有任何从属关系
  • Java是一样的

[外链图片转存中…(img-Md4Q8pqT-1625207288225)]

9.4.2 协变

语法格式

代码语言:javascript
复制
class Pair[+T]
  • 类型B是A的子类型,Pair[B]可以认为是Pair[A]的子类型
  • 参数化类型的方向和类型的方向是一致的。
9.4.3 逆变

语法格式

代码语言:javascript
复制
class Pair[-T]
  • 类型B是A的子类型,Pair[A]反过来可以认为是Pair[B]的子类型
  • 参数化类型的方向和类型的方向是相反的

示例

示例说明

  • 定义一个Super类、以及一个Sub类继承自Super类
  • 使用协变、逆变、非变分别定义三个泛型类
  • 分别创建泛型类来演示协变、逆变、非变

参考代码

代码语言:javascript
复制
class Super
class Sub extends Super

class Temp1[T]
class Temp2[+T]
class Temp3[-T]

def main(args: Array[String]): Unit = {
    val a:Temp1[Sub] = new Temp1[Sub]
    // 编译报错
    // 非变
    //val b:Temp1[Super] = a

    // 协变
    val c: Temp2[Sub] = new Temp2[Sub]
    val d: Temp2[Super] = c

    // 逆变
    val e: Temp3[Super] = new Temp3[Super]
    val f: Temp3[Sub] = e
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-07-02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Scala:样例类、模式匹配、Option、偏函数、泛型
    • 1. 样例类
      • 1.1 定义样例类
      • 1.2 定义一个样例类
      • 1.3 可变成员变量
      • 1.4 样例类的方法
    • 2. 样例对象
      • 2.1 定义
    • 3. 模式匹配
      • 3.1 简单模式匹配
      • 3.2 匹配类型
      • 3.3 守卫
      • 3.4 匹配样例类
      • 3.5 匹配集合
      • 3.6 匹配数组
      • 3.7 匹配列表
      • 3.8 匹配元组
      • 3.9 变量声明中的模式匹配
    • 4. Option类型
      • 5. 偏函数
        • 6. 正则表达式
          • 7. 异常处理
            • 7.1 捕获异常
            • 7.2 抛出异常
          • 8. 提取器(Extractor)
            • 8.1 定义提取器
          • 9. 泛型
            • 9.1 定义一个泛型方法
            • 9.2 泛型类
            • 9.3 上下界
            • 9.4 协变、逆变、非变
        相关产品与服务
        对象存储
        对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档