【scala系列】11、泛型、上下边界、协变、逆变


1. 泛型类

  • 泛型类就是在类的声明中,定义一些泛型类型。然后类内部的字段或者方法就可以使用这些泛型类型
  • 使用泛型类通常是需要对类中的某些成员(字段、方法中的参数或变量)进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性
  • 如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中,难免会出现问题,比如传入了不希望的类型,导致程序出问题
  • 在使用类的时候,比如创建类的对象,将类型参数替换为实际类型即可
  • scala自动推断泛型类型特性即直接给使用了泛型类型的字段赋值时,scala会自动进行类型推断
  • 泛型类型与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内的多个变量或者返回值之间就可以使用泛型类型进行声明,从而对某个特殊的变量或者多个变量进行强制的类型限制。
  • 与泛型类一样你可以通过给使用了泛型类型的变量传递值让scala自动推断泛型的实际类型,也可以在调用函数时手动指定泛型类型

1.1 类中的范型


class GenericDemo1[T1,T2](var name:T1){

  // 初始值为T类型的缺省值
  private var v:T1 = _

  def setV(v1:T1) = { v = v1 }
  def getV = v

  var age :T2 = _

  def say()= {
    println(s"hello $name")
  }

}

object GenericDemo1 extends App{
    //根据类实例化时泛型限制,name字段只能传入字符串
    val c1 = new GenericDemo1[String,Int]("小明")
    c1.say() //hello 小明

    c1.age = 12 //age 只能赋值为 T2 类型也就是Int

    println(s"${c1.name} age is ${c1.age}") //小明 age is 12

    c1.setV("value")
    println(c1.getV) // value


    val c2 = new GenericDemo1[Int,String](123)
    c2.setV(123)
    println(c2.getV) // 123
}

1.2 函数中的泛型

object ClassDemo02 {
  def main(args: Array[String]): Unit = {
    val c01 = ClassDemo02
    println(say[String,Int,String]("小明",12)) //小明<=>12
  }
  def say[T1,T2,B1](name:T1,age:T2):B1={
    (name+"<=>"+age).asInstanceOf[B1]
  }
}

object GenericDemo2_1 extends App{

  /**
    * 定义有三个泛型的say方法
    * @return
    */
  def say[T1](value:T1):Unit={
    println(s"value.getClass = ${value.getClass}")
    println(s"value is $value")
  }

  // 调用参数为泛型的方法,分别指定参数类型为 String,Int,String
  say[String]("小明")
  /**
    * value.getClass = class java.lang.String
    * value is 小明
    */
  // 如果指定泛型类型,那么传参时必须跟指定的泛型类型一致
  say[Int](12)
  /**
    * value.getClass = class java.lang.Integer
    * value is 12
    */

  // 如果没有指定泛型类型会根据参数判断类型
  say(0.12)
  /**
    * value.getClass = class java.lang.Double
    * value is 0.12
    */
}

1.3 泛型定义type


object GenericDemo3 {

  def main(args: Array[String]): Unit = {
    println(m1("xiaom").name) // xiaom
    println(m2(List(1,2,3,4,5)).len) // 5
  }
  def m1(name1:String) = new GenericDemo3_1 {
    type T = String
    val name = name1
  }

  def m2(e1:List[Int]) = new GenericDemo3_2 {
    type T = Int
    val list = e1
  }
}

abstract class GenericDemo3_1 {
  type T
  val name:T
}

abstract class GenericDemo3_2 {
  type T
  val list:List[T]
  def len = list.length
}

1.4 泛型定义枚举(Enum)

Scala没有在语言层面定义Enumeration,而是在库中实现


object GenericDemo4 extends App{

  println(Color.RED)

  println(Color.values) //Color.ValueSet(红色, 绿色, 蓝色, 黑色, 白色)

  println("-----")
  val colorful = Color.values.filterNot(List("黑色","白色") contains _.toString)
  colorful.foreach(println(_))

  // 同
  // colorful foreach println
  /*
  红色
  绿色
  蓝色
   */

}

object Color extends Enumeration{
  val RED   = Value("红色")
  val GREEN = Value("绿色")
  val BLUE  = Value("蓝色")
  val WHITE = Value("黑色")
  val BLASK = Value("白色")
}

2. 上边界与下边界

2.1 上边界

上边界用来判断我传参类型最大为那个类型

上边界: <: (小于号冒号)即泛型T,只能是 GenericDemo5_2 或其子类


class GenericDemo5_1

class GenericDemo5_2 extends GenericDemo5_1

class GenericDemo5_3 extends GenericDemo5_2

//上边界: <: (小于号冒号) 即泛型T,只能是 GenericDemo5_2 或者其子类 (这里只能传 GenericDemo5_2 或 GenericDemo5_3)
class GenericDemo5[T <: GenericDemo5_2] {
  def say(p: T) = {
    println(p.getClass)
  }
}

object GenericDemo5 extends App {

  // 实例化类GenericDemo5 传入泛型上边界为GenericDemo5_2,
  // 所以在本测试代码中,符合条件的有GenericDemo5_3,GenericDemo5_3
  val c5= new GenericDemo5[GenericDemo5_2]

  // GenericDemo5_1 是GenericDemo5_2的父类,所以这里不能传GenericDemo5_1
  //c5.say(new GenericDemo5_1)
  // Type mismatch, expected: GenericDemo5_2, actual: GenericDemo5_1

  c5.say(new GenericDemo5_2)
  //class hnbian.scala.classobject.generic.GenericDemo5_2

  c5.say(new GenericDemo5_3)
  //class hnbian.scala.classobject.generic.GenericDemo5_3


  // 实例化类GenericDemo5 传入泛型上边界为GenericDemo5_3,
  // 所以在本测试代码中,符合条件的只有GenericDemo5_3 一个类
  val c5_3= new GenericDemo5[GenericDemo5_3]
  // c5_3.say(new GenericDemo5_2)
  // Type mismatch, expected: GenericDemo5_3, actual: GenericDemo5_2

  c5_3.say(new GenericDemo5_3)
  //class hnbian.scala.generics.GenericDemo5_3
}

2.2 下边界

下边界: >: (大于号冒号) 即 T 只能是 GenericDemo6_2 或其父类


class GenericDemo6_1

class GenericDemo6_2 extends GenericDemo6_1

class GenericDemo6_3 extends GenericDemo6_2

//下边界:>: (大于号冒号) 即 T 只能是GenericDemo6_2 或者其父类
class GenericDemo6 [ T >: GenericDemo6_2 ]{
  def say(p:T): Unit ={
    println(p.getClass)
  }
}


object GenericDemo6 extends App{


  // 实例化类GenericDemo6 传入泛型上边界为GenericDemo6_2,
  // 所以在本测试代码中,符合条件的有GenericDemo6_3,GenericDemo6_3
  val c6 = new GenericDemo6[GenericDemo6_2]

  // 这里不能传GenericDemo6_1 类型因为定义类的时候 参数类型为GenericDemo6_2
  //c6.say(new GenericDemo6_1) 
  // Type mismatch, expected: GenericDemo6_2, actual: GenericDemo6_1

  c6.say(new GenericDemo6_2) 
  //class hnbian.scala.classobject.generic.GenericDemo6_2

  c6.say(new GenericDemo6_3) 
  //class hnbian.scala.classobject.generic.GenericDemo6_3


  val c6_1= new GenericDemo6[GenericDemo6_1]

  c6_1.say(new GenericDemo6_1) 
  //class hnbian.scala.classobject.generic.GenericDemo6_1

  c6_1.say(new GenericDemo6_2) 
  //class hnbian.scala.classobject.generic.GenericDemo6_2

  c6_1.say(new GenericDemo6_3) 
  //class hnbian.scala.classobject.generic.GenericDemo6_3


  // 这里不能传GenericDemo6_3 因为不符合条件
  //val c6_3= new GenericDemo6[GenericDemo6_3]

}

3. 协变与逆变

协变是scala中比较有特色的功能,它完美解决了java中泛型的一些缺陷

比如 P 是M 的子类, 那么 C[P] 是不是C[M] 的子类呢

在java中很遗憾不是,这给我们编码带来了很多局限性,scala的协变完美的解决了这个问题,其实协变就是泛型上边界的一种加强

3.1 协变


class GenericDemo7_1

class GenericDemo7_2 extends GenericDemo7_1

class GenericDemo7_3 extends GenericDemo7_2

//+T 声明协变,即 C[GenericDemo7_2] 也是 C[GenericDemo7_1s]  的子类
class C[+T]

class GenericDemo7 {
  //只有 GenericDemo7_2 及其以下的类才能传入
  def say(c:C[GenericDemo7_2]) = {
    println(c.getClass)
  }
}

object GenericDemo7 extends App{

  val c7 = new GenericDemo7

  // 不能 传 C[GenericDemo7_1]
  // c7.say(new C[GenericDemo7_1])
  // Type mismatch, expected: C[GenericDemo7_2], actual: C[GenericDemo7_1]

  c7.say(new C[GenericDemo7_2])
  //class hnbian.scala.classobject.generic.C

  c7.say(new C[GenericDemo7_3])
  //class hnbian.scala.classobject.generic.C

  c7.say(new C{})
  //class hnbian.scala.classobject.generic.GenericDemo7$$anon$1

}

3.2 逆变

逆变 scala中比较有特色的功能,它完美解决了java中泛型的一些缺陷

比如 P 是M 的子类, 那么 C[P] 是不是C[M] 的子类呢

在java中很遗憾不是,这给我们编码带来了很多局限性,scala的逆变完美的解决了这个问题,

其实逆变就是泛型下边界的一种加强


class GenericDemo8_1

class GenericDemo8_2 extends GenericDemo8_1

class GenericDemo8_3 extends GenericDemo8_2

//+T 声明协变,即 C[GenericDemo8_2]  也是 C[GenericDemo8_1]  的子类
class C[-T]

class GenericDemo8 {
  //只有 GenericDemo8_2 及其以上的类才能传入
  def say(c: C[GenericDemo8_2]) = {
    println(c.getClass)
  }
}

object GenericDemo8 extends App {

  val c8 = new GenericDemo8

  c8.say(new C[GenericDemo8_1])
  // class hnbian.scala.classobject.generic.C

  c8.say(new C[GenericDemo8_2])
  // class hnbian.scala.classobject.generic.C

  // c8.say(new C[GenericDemo8_3])
  // Type mismatch, expected: C[GenericDemo8_2], actual: C[GenericDemo8_3]

  c8.say(new C {})
  // class hnbian.scala.classobject.generic.GenericDemo8$$anon$1

}

文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
 上一篇
【scala系列】12、隐式转换、视图界定 【scala系列】12、隐式转换、视图界定
1. 隐式转换 隐式转换是scala中一种特殊的功能,能在不改动已有class设计的情况下为class添加新的方法 隐式转换是把一种类型安全地转成另一种类型,原数据类型将拥有新的数据类型的所有方法,也可以看成是对类的一种增强 定义隐式转换的
2020-04-18
下一篇 
【scala系列】10、class(类)、object(对象)、trait(特质) 【scala系列】10、class(类)、object(对象)、trait(特质)
1. 对象(object)在scala 中被object 关键字修饰的类有一下特征 没有 有参数的主构造器, 但是有主构造器代码块(不包含在任何方法中的代码,就是主构造器代码) 它是单例的所以主构造器代码块只会执行一次 不需要通过关键字
2020-04-12
  目录