Scala Generic 泛型类详解 - T

本文深入探讨Scala中的泛型,包括泛型类、泛型方法、高阶函数、闭包、柯里化、泛型约束、视图界定、上下文界定、裂变与逆变,以及ClassTag和Manifest的使用。通过实例展示了如何在不同场景下使用泛型,以及泛型在类型安全和代码复用上的优势。
摘要由CSDN通过智能技术生成

一.引言:

正常编程时,我们的类初始化参数或者方法参数都是指定的对象,例如 def sum(arr: Array[Int]) 这样,这时如果传入 arr: Array[String] 就会提示参数不合规,这时候可以通过隐式转换implcit的方法,使得参数合法化,还有一种方法就是使用泛型。泛型一般是为了适配多个场景,多见于scala各种类的源码中。例如Array的构造,我们可以构造Array[Int],也可以构造Array[String]等等,翻看源码这时候经常看到原始类是这样定义的即Array[T],这里 T 就代表泛型,他可以代表任何对象,再如上一篇文章说到的 Map 的构造方法源码中,它的定义方法就是泛型,因为一个 Map的key-value总可能是各式各样的对象:


   
   
  1. abstract class GenMapFactory[CC[A, B] <: GenMap[ A, B] with GenMapLike[ A, B, CC[ A, B]]] {
  2. /** The type constructor of the collection that can be built by this factory */
  3. type Coll = CC[_, _]
  4. /** The default builder for $Coll objects.
  5. * @tparam A the type of the keys
  6. * @tparam B the type of the associated values
  7. */
  8. def newBuilder[ A, B]: Builder[( A, B), CC[ A, B]] = new MapBuilder[ A, B, CC[ A, B]](empty[ A, B])
  9. /** The standard `CanBuildFrom` class for maps.
  10. */
  11. class MapCanBuildFrom[A, B] extends CanBuildFrom[Coll, (A, B), CC[A, B]] {
  12. def apply(from: Coll) = newBuilder[ A, B]
  13. def apply() = newBuilder
  14. }
  15. }

这里A,B,CC都代表一类泛型。

二.编写泛型类常用的语法

1.高阶函数

高阶函数就是将函数当做参数传入,argFunction 代表一个参数为Double,返回值也是Double的函数,将来调用highFunction时,需要传入一个函数签名满足argFunction条件的函数。


   
   
  1. def highFunction(argFunction: ( Double) => ( Double)): Double = argFunction( 10)
  2. def multiply(num: Double): Double = num * 1.0
  3. println(highFunction(multiply))

2.闭包

闭包就是能够读取其他函数内部变量的函数,且内函数可以访问外函数的变量:


   
   
  1. var total = 0
  2. def outFunction(): Int = {
  3. def innerFunction(): Int = {
  4. total += 1
  5. total
  6. }
  7. innerFunction()
  8. }
  9. println(outFunction())
  10. println(outFunction())
  11. println(outFunction())
 
   
   

3.柯里化

柯里化在平常使用中经常见到,就是将具有多个参数的函数转化为一条函数链 ,每个节点是一个单一参数,有点类似于构造器的设计模式。下面两种构造方式实现相同功能,其中第二中就是柯里化的样式。


   
   
  1. def add(x: Int, y: Int): Int = x + y
  2. def addNew(x: Int)(y: Int): Int = x + y
  3. println(add( 1, 2))
  4. println(addNew( 1)( 2))

三.泛型详解

1.泛型方法

泛型T的引入使得方法可以找到任意数组的中间元素,当然也要区分具体场景,如果我每次都要得到数组各个元素的和,那么就无法实现泛型。


   
   
  1. def getMiddle(arr: Array[ Int]): Int = {
  2. arr(arr.length / 2)
  3. def getMiddleNew[ T](arr: Array[ T]): T = {
  4. arr(arr.length / 2)
  5. }
  6. val arr1 = Array( 1, 2, 3, 4, 5)
  7. val arr2 = Array( "a", "b", "c", "d", "e")
  8. println(getMiddleNew(arr1))
  9. println(getMiddleNew(arr2))

当然也可以更泛型一些,这里结合了高阶函数与柯里化,除了元素的泛型外,函数的形式也只做了笼统的要求。


   
   
  1. def getMiddle[ T](handle: T)(func: T => Any): Any = {
  2. func(handle)
  3. }

   
   
  1. val arr = Array( 1, 2, 3, 4, 5)
  2. def getMid(arr: Array[ Int]): Unit = {
  3. println(arr(arr.length / 2))
  4. }
  5. getMiddleHandle(arr)(getMid)

2.泛型类

这里构造了泛型类,使得类更容易扩展。


   
   
  1. class commonClass {
  2. private var int = 0
  3. def set(num: Int): Unit = this.int = num
  4. def get(): Int = int
  5. }
  6. class genericT[T] {
  7. private var content: T = _
  8. def set(value: T): Unit = this.content = value
  9. def get(): T = content
  10. }

3.泛型变量界定

翻看源码时,也会看到如下标识 S <: T 或者 S >: T,这里 :

S <: T S必须是T的子类或者同类
S >: T T必须是S的子类或者同类

下面定义了工具类,工具类的子类车辆类,以及车辆的子类轿车类以及乘坐车辆类的方法,这里要求调用driver方法的类必须是Vehicle的子类或同类。


   
   
  1. class Tool() {
  2. def driver(): Unit = println( "Tool Using")
  3. }
  4. class Vehicle() extends Tool {
  5. override def driver(): Unit = println( "Driving")
  6. }
  7. class Car extends Vehicle {
  8. override def driver(): Unit = println( "Car Driving")
  9. }
  10. def takeVehicle[ T <: Vehicle](v: T): Unit = v.driver()

这里初始化工具类,车辆类与轿车类, 由于 T <: Vehicle 的约束,这里vehicle和bicycle可以调用 takeVehicle方法,而tool则因为是Vehicle的父类而无法调用。


   
   
  1. val tool = new Tool()
  2. val vehicle = new Vehicle()
  3. val bicycle = new Bicycle()
  4. takeVehicle(tool)
  5. takeVehicle(vehicle)
  6. takeVehicle(bicycle)

Scala的HashMap的构造方法就是使用了泛型的约束:


   
   
  1. abstract class MutableMapFactory[CC[A, B] <: mutable. Map[ A, B] with mutable. MapLike[ A, B, CC[ A, B]]]
  2. extends MapFactory[ CC] {
  3. /** The default builder for $Coll objects.
  4. * @tparam A the type of the keys
  5. * @tparam B the type of the associated values
  6. */
  7. override def newBuilder[ A, B]: Builder[( A, B), CC[ A, B]] = empty[ A, B]
  8. }

这里CC[A,B]代表的kv结构必须是 mutable.Map[A,B]的子类或者同类。

4.视图界定

T <% M 泛型视图界定符,表示把传入不是M[T]类型的隐式传换为M[T],这里常见的就是Comparable,Oerdring等等。


   
   
  1. class Compare[T <% Comparable[T]](val object1: T, val object2: T) {
  2. def compare(): T = {
  3. if (object1.compareTo(object2) > 0) object1
  4. else object2
  5. }
  6. }

这样写可以通过编译,但是scala代码建议使用因残式参数更好,所以这里我们采用另一种写法,这里implicit隐藏转换其实和 <% 的作用是一样的:


   
   
  1. class CompareImplicit[T](val object1: T, val object2: T)(implicit object2Compare: T => Comparable[T]) {
  2. def compare(): T = {
  3. if (object1.compareTo(object2) > 0) object1
  4. else object2
  5. }
  6. }

接下来我们需要实现这个T => Comparable[T]的转换:


   
   
  1. class person(val age: Int) {
  2. def getAge: Int = age
  3. }
  4. implicit def toComparable(p: person): Comparable[person] = new Comparable[person] {
  5. override def compareTo(o: person): Int = {
  6. if (p.getAge > o.getAge) 1
  7. else 0
  8. }
  9. }

测试一下都是ok的:


   
   
  1. val p1 = new person( 10)
  2. val p2 = new person( 20)
  3. val compare = new Compare(p1,p2)
  4. val compareImplicit = new CompareImplicit(p1,p2)
  5. println(compare.compare().getAge)
  6. println(compareImplicit.compare().getAge)

5.上下文界定

上下文界定其实和上面的视图界定比较像,区别就是视图界定需要一个隐式方法将对应对象变为M[T],而上下文界定则是要求存在一个M[T]的隐式值,区别就是方法和值。Compare类定义了一个上下界Ordering,在其作用域内,必须存在一个Ordering[T]的隐式值,而且这个隐式值可以应用与内部的方法,放到当下场景就是该隐式值可以支持两个类进行比较。这里使用了两种定义方法,第一种利用了柯里化添加了隐式参数,可以看到柯里化频繁应用在泛型类,泛型方法的定义中,第二种并没有在参数中显示的表明需要隐式的参数,而是通过implicitly关键字拿到上下文的对象M[T],然后就是上面说的,该隐式值可以应用到内部方法。


   
   
  1. class Compare[T: Ordering] {
  2. def compareImplicit(first: T, second: T)( implicit ord: T => Ordered[ T]): T = {
  3. if (first > second) first
  4. else second
  5. }
  6. def compareImplicitly(first: T, second: T): T = {
  7. val order = implicitly[ Ordering[ T]]
  8. if (order.gt(first, second)) first
  9. else second
  10. }
  11. }

PersonOrdering继承了Ordering[T],所以实现了下面的compare方法。


   
   
  1. class PersonOrdering extends Ordering[person] {
  2. override def compare(x: person, y: person): Int = {
  3. if (x.age > y.age) 1 else -1
  4. }
  5. }

注意这里上下文界定需要的是值,所以还需要将隐式值初始化好。

implicit val order = new PersonOrdering
   
   

测试一下,由于我们实现了上下文界定要求的ord,所以这里直接初始化调用函数即可达到需求:


   
   
  1. val c: Compare[person] = new Compare[person]
  2. val p1 = new person( 10)
  3. val p2 = new person( 20)
  4. println(c.compareImplicit(p1, p2).getAge)
  5. println(c.compareImplicitly(p1, p2).getAge)

6.裂变与逆变

协变:Scala的类或特征的范型定义中,如果在类型参数前面加入+符号,就可以使类或特征变为协变了。泛型变量的值可以是本身类型或者其子类的类型

逆变:在类或特征的定义中,在类型参数之前加上一个-符号,就可定义逆变范型类和特征了。泛型变量的值可以是本身类型或者其父类的类型

裂变与逆变其实和上面的泛型约束比较相似,只不过更加抽象,适用性更加广泛。


   
   
  1. class Tool() {
  2. def driver(): Unit = println( "Tool Using")
  3. }
  4. class Vehicle() extends Tool {
  5. override def driver(): Unit = println( "Driving")
  6. }
  7. class Car extends Vehicle {
  8. override def driver(): Unit = println( "Car Driving")
  9. }
  10. class UsingTool[+T](t: T){}
  11. class TakeVehicle[-T](t: T){}

继续使用汽车的例子,这里UsingTool使用+T,代表泛型的值可以是本身类型或子类,TakeVechicle使用了-T,代表参数可以是本身或父类。如果颠倒父类与子类的关系,则会提示是不符合的type,代码无法编译。


   
   
  1. // +T 泛型的值可以是本身或者子类
  2. val carVehicle: UsingTool[ Car] = new UsingTool[ Car]( new Car)
  3. val tool: UsingTool[ Tool] = carVehicle
  4. // -T 泛型的值可以是本身或者父类
  5. val vehicle: TakeVehicle[ Vehicle] = new TakeVehicle[ Vehicle]( new Vehicle())
  6. val car: TakeVehicle[ Car] = vehicle

7.多重界定


   
   
  1. T <: A with B
  2. => ABT上界
  3. T >: A with B
  4. => ABT下界
  5. T >: A <: B
  6. => A为上届 B为下界且 AB的子类,类似于继承关系判断
  7. T: A: B
  8. => 类型变量界定,即同时满足 A[ T]这种隐式值和 B[ T]这种隐式值
  9. T <% A <% B
  10. => 视图界定,即同时能够满足隐式转换的 A和隐式转换的 B

8.ClassTag

按照官方文档的说法,ClassTag存储给定T的,可以通过"运行时"类访问字段,对于实例化元素类型未知的Array有用。通俗一点解释就是它包含了T的类型信息,该类型信息将用于数组创建。


   
   
  1. def apply[ T](runtimeClass1: jClass[_]): ClassTag[ T] =
  2. runtimeClass1 match {
  3. case java.lang. Byte. TYPE => ClassTag. Byte.asInstanceOf[ ClassTag[ T]]
  4. case java.lang. Short. TYPE => ClassTag. Short.asInstanceOf[ ClassTag[ T]]
  5. case java.lang. Character. TYPE => ClassTag. Char.asInstanceOf[ ClassTag[ T]]
  6. case java.lang. Integer. TYPE => ClassTag. Int.asInstanceOf[ ClassTag[ T]]
  7. case java.lang. Long. TYPE => ClassTag. Long.asInstanceOf[ ClassTag[ T]]
  8. case java.lang. Float. TYPE => ClassTag. Float.asInstanceOf[ ClassTag[ T]]
  9. case java.lang. Double. TYPE => ClassTag. Double.asInstanceOf[ ClassTag[ T]]
  10. case java.lang. Boolean. TYPE => ClassTag. Boolean.asInstanceOf[ ClassTag[ T]]
  11. case java.lang. Void. TYPE => ClassTag. Unit.asInstanceOf[ ClassTag[ T]]
  12. case ObjectTYPE => ClassTag. Object.asInstanceOf[ ClassTag[ T]]
  13. case NothingTYPE => ClassTag. Nothing.asInstanceOf[ ClassTag[ T]]
  14. case NullTYPE => ClassTag. Null.asInstanceOf[ ClassTag[ T]]
  15. case _ => new ClassTag[ T]{ def runtimeClass = runtimeClass1 }
  16. }

可以利用上面的mkArray初始化各种类型的数组: 

    def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*)

   
   

   
   
  1. val numArr = mkArray( 42, 13)
  2. println(numArr.map(_ + 3).mkString( " "))
  3. val strArr = mkArray( "Japan", "Brazil", "Germany")
  4. println(strArr.map(_ + "ss").mkString( " "))
  5. val personArr = mkArray(p1,p2)
  6. personArr.map(p => p.getAge)

初始化数组如果不添加ClassTag或者Manifest关键字,编译器会提示你无法初始化数组,因为Scala运行时,数组必须具有具体的类型。

    def mkArrayNoClassTag[T](elems: T*) = Array[T](elems: _*)

   
   

编译时会提示没有隐式的推断参数供scala判断数组类型:

9.Manifest

Manifest是类型T的描述符,编译器使用它来保存必要的信息用于实例化数组,并作为参数用在方法运行的上下文。

上面classTag的例子同样也可以用Manifest实现:

    def mkArrayWithManifest[T : Manifest](elems: T*) = Array[T](elems: _*)

   
   

   
   
  1. val numArrWithManifest = mkArrayWithManifest( 42, 13)
  2. println(numArrWithManifest.map(_ + 3).mkString( " "))
  3. val strArrWithManifest = mkArrayWithManifest( "42", "13")
  4. println(strArrWithManifest.map(_ + "3").mkString( " "))

但是换另一个例子,ClassTag和Mainfest就有区别,使用mkArray生成不用元素数组时,用ClassTag关键字的可以打出来,但是使用Manifest却会报类型异常:

    mkArray(Array(0,1),Array("1","2")).foreach(x => println(x.mkString(" ")))

   
   

   
   
  1. 0 1
  2. 1 2
    val mixArrManifest = mkArrayWithManifest(Array(0,1),Array("1","2"))

   
   

Manifest还有一个用处就是可以查看类型:

  def manOf[T:Manifest](t:T): Manifest[T] = manifest[T]

   
   

   
   
  1. val mixArr = mkArray( Array( 0, 1), Array( "1", "2"))
  2. println(manOf(mixArr))
Array[Array[_ >: java.lang.String with Int <: Any]]

   
   

这里是不是看到了多重界定的影子。

10.类型约束


   
   
  1. // A =:= B // 表示A类型等同于B类型
  2. // A <:< B // 表示A类型是B类型的子类

   
   
  1. class animal extends java.io.Serializable {}
  2. def checkIsSerialzable[ T](t: T)( implicit obj: T <:< java.io. Serializable): Unit = {
  3. println( "true")
  4. }

可以调用:

    checkIsSerialzable(new animal())

   
   

不可以调用:

    checkIsSerialzable(new person(10))

   
   

泛型大概就说这么多,发现在学习过程中对type,以及一些关键字的概念还是不熟悉,需要后续继续加深理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值