Scala之泛型

目录

  1. 泛型
  2. 上界
  3. 下界
  4. 非变
  5. 斜变
  6. 逆变

泛型

泛型的意思是 泛指某种具体的数据类型 , 在Scala中, 泛型用 [ 数据类型] 表示. 在实际开发中, 泛型一般是结合数组或者集合来使用的, 除此之外, 泛型的常见用法还有以下三种:

  • 泛型方法
  • 泛型类
  • 泛型特质

泛型方法

泛型方法指的是 把泛型定义到方法声明上 , 即:该方法的参数类型是由泛型来决定的 . 在调用方法时, 明确具体的数据类型.

格式

def 方法名[泛型名称](..) = {
//...
}

需求

定义方法getMiddleElement(), 用来获取任意类型数组的中间元素.

  • 思路一 : 不考虑泛型直接实现(基于Array[Int]实现)
  • 思路二 : 加入泛型支持.

参考代码

def main(args: Array[String]): Unit = {
val arr1 = Array(1, 2, 3, 4, 5, 6)
val arr2 = Array("a", "b", "c", "d", "e", "f")

println(getMiddleElement(arr1))
println(getMiddleElement(arr2))
}


// 泛型方法在调用方法的时候 明确具体的数据类型.
def getMiddleElement[T](arr: Array[T]): T = {
arr(arr.length / 2)
}

泛型类

泛型类指的是 把泛型定义到类的声明上 , 即:该类中的成员的参数类型是由泛型来决定的 . 在创建对象时, 明确具体的数据类型.

格式

class [T](val 变量名: T)

需求

  1. 定义一个泛型类, 该类包含两个字段,且两个字段的类型不固定.
  2. 创建不同类型的泛型类对象,并打印.

参考代码

// 泛型类,在创建对象的时候, 明确具体的数据类型.
class GenericClass[T] (val a:T, val b:T){

def printElement(ele:T): Unit ={
println(ele)
}
}

object GenericClass{
def main(args: Array[String]): Unit = {

val generic1 = new GenericClass[String]("zhangsan", "喜欢女孩")
val generic2= new GenericClass[Int](18, 10)

println(generic1.a)
generic1.printElement("hehe")
println(generic2.a)
generic2.printElement(15)
}
}

泛型特质

泛型特质指的是 把泛型定义到特质的声明上 , 即:该特质中的成员的参数类型是由泛型来决定的 . 在定义泛型特质的子类或者子单例对象时, 明确具体的数据类型.

格式

trait 特质A[T] {
//特质中的成员
}
class 类B extends 特质A[指定具体的数据类型] {
//类中的成员
}
123456

需求

  1. 定义泛型特质, 该类有一个变量a和show()方法, 它们都是用特质的泛型.
  2. 定义单例对象, 继承特质.
  3. 打印单例对象中的成员.

参考代码

// 泛型特质指的是 把泛型定义到特质的声明上 ,
// 即:该特质中的成员的参数类型是由泛型来决定的 .
// 在定义泛型特质的子类或者子单例对象时, 明确具体的数据类型.
trait GenericTrait[T] {
def show(a:T): Unit = println(a)
}


class GenericTraitA[E] extends GenericTrait[E] {}

class GenericTraitB extends GenericTrait[Int] {}

object GenericTrait {
def main(args: Array[String]): Unit = {
val a = new GenericTraitA[String]
a.show("zs")

val b = new GenericTraitB
b.show(1)

}
}

上下界

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

上界

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

格式

[T <:  类型]

例如: [T <: Person]的意思是, 泛型T的数据类型必须是Person类型或者Person的子类型

需求

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

参考代码

class Person(val name:String){
override def toString: String = name
}

class Student(override val name: String) extends Person(name){
override def toString: String = name
}


object UpperBound {

def printlnArr[T <: Person](arr:Array[T]): Unit = arr.foreach(x => println(x.toString))

def main(args: Array[String]): Unit = {

val arr1 = Array(1,2,3,4)
val arr2 = Array(new Student("zs"),new Student("ls"),new Student("ww"))

// println(printlnArr(arr1)) //不符合上界类型,会报错
printlnArr(arr2)

}
}

下界

使用 T >: 数据类型 表示给类型添加一个下界,表示泛型参数必须是从该类型本身或该类型的父类型.

格式

[ T >: 类型]

注意:

  1. 例如: [T >: Person]的意思是, 泛型T的数据类型必须是Person类型或者Person的父类型
  2. 如果泛型既有上界、又有下界。下界写在前面,上界写在后面. 即: [T >: 类型1 <: 类型2]

需求

  1. 定义一个PersonMan类
  2. 定义一个PoliceMan类,继承PersonMan类
  3. 定义一个SuperMan类,继承PoliceMan类
  4. 定义一个demo泛型方法,该方法接收一个Array参数,
  5. 限定demo方法的Array元素类型只能是PersonMan、PersonMan
  6. 测试调用demo,传入不同元素类型的Array

参考代码

class PersonMan(val name:String){
override def toString: String = name
}

class PoliceMan(override val name: String) extends PersonMan(name){
override def toString: String = name
}

class SuperMan(override val name: String) extends PoliceMan(name){
override def toString: String = name
}

object DownBound {

// 下界 >:
def printlnArr[T >: PoliceMan](array: Array[T]): Unit = array.foreach(x => println(x.toString))

def main(args: Array[String]): Unit = {

val arr1 = Array(new SuperMan("z3"),new SuperMan("l4"),new SuperMan("w5"))
val arr2 = Array(new PersonMan("z3"),new PersonMan("l4"),new PersonMan("w5"))

// printlnArr(arr1) // 不符合下界类型,会报错
printlnArr(arr2)

}
}

型变

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

  • 非变 : 类A和类B之间是父子类关系, 但是Pair[A]和Pair[B]之间没有 任何关系 .
  • 协变 : 类A和类B之间是父子类关系, Pair[A]和Pair[B]之间也有 父子类 关系.
  • 逆变 : 类A和类B之间是父子类关系, 但是Pair[A]和Pair[B]之间是 子父类 关系.

如下图:

非变

语法格式

class Pair[T]{}
  • 默认泛型类是 非变的
  • 即 : 类型B是A的子类型,Pair[A]和Pair[B]没有任何从属关系

斜变

语法格式

class Pair[+T]
  • 类型 B是A的子类型,Pair[B]可以认为是Pair[A]的子类型
  • 参数化类型的方向和类型的方向是一致的。

逆变

语法格式

class Pair[-T]
  • 类型 B是A的子类型,Pair[A]反过来可以认为是Pair[B]的子类型
  • 参数化类型的方向和类型的方向是相反的

示例

需求

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

参考代码

// 父类
class Super()

// 子类
class Sub() extends Super()

// 协变 : 类A和类B之间是父子类关系, Pair[A]和Pair[B]之间也有 父子 类关系.
class Covariant[+T](val name: String)

// 逆变 : 类A和类B之间是父子类关系, 但是Pair[A]和Pair[B]之间是 子父 类关系.
class Contravariant[-T](val name: String)

// 非变 : 类A和类B之间是父子类关系, 但是Pair[A]和Pair[B]之间 没有任何 关系 .
class Invariant[T]()

object TypeChange {

def main(args: Array[String]): Unit = {
val invSub = new Invariant[Sub]()
// val invSuper: Invariant[Super] = invSub // 编译报错 Sub和Super是父子关系,但是Invariant[Sub]和Invariant[Super]没关系


val covSub = new Covariant[Sub]("zs")
val covSuper: Covariant[Super] = covSub
println(covSuper.name) //zs

val conSub = new Contravariant[Sub]("zs")
// val conSuper: Contravariant[Super] = conSub // Super是Sub是父类,逆变后Contravariant[Super]是Contravariant[Sub]的子类

val conSub2 = new Contravariant[Super]("zs")
val conSuper2: Contravariant[Sub] = conSub2
println(conSuper2.name) // zs

}
}
Author: Tunan
Link: http://yerias.github.io/2020/12/20/scala/6/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.