Scala Generic 泛型类详解 - T

一.引言:

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

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

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

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

1.高阶函数

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

    def highFunction(argFunction: (Double) => (Double)): Double = argFunction(10)
    def multiply(num: Double): Double = num * 1.0

    println(highFunction(multiply))

2.闭包

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

    var total = 0
    def outFunction(): Int = {
      def innerFunction(): Int = {
        total += 1
        total
      }
      innerFunction()
    }

    println(outFunction())
    println(outFunction())
    println(outFunction())
 

3.柯里化

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

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

三.泛型详解

1.泛型方法

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

    def getMiddle(arr: Array[Int]): Int = {
        arr(arr.length / 2)

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

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

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

2.泛型类

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

    class commonClass {
      private var int = 0
      def set(num: Int): Unit = this.int = num
      def get(): Int = int
    }

    class genericT[T] {
      private var content: T = _
      def set(value: T): Unit = this.content = value
      def get(): T = content
    }

3.泛型变量界定

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

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

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

  class Tool() {
    def driver(): Unit = println("Tool Using")
  }

  class Vehicle() extends Tool {
    override def driver(): Unit = println("Driving")
  }

  class Car extends Vehicle {
    override def driver(): Unit = println("Car Driving")
  }


  def takeVehicle[T <: Vehicle](v: T): Unit = v.driver()

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

    val tool = new Tool()
    val vehicle = new Vehicle()
    val bicycle = new Bicycle()
    takeVehicle(tool)
    takeVehicle(vehicle)
    takeVehicle(bicycle)

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

abstract class MutableMapFactory[CC[A, B] <: mutable.Map[A, B] with mutable.MapLike[A, B, CC[A, B]]]
  extends MapFactory[CC] {

  /** The default builder for $Coll objects.
   *  @tparam A      the type of the keys
   *  @tparam B      the type of the associated values
   */
  override def newBuilder[A, B]: Builder[(A, B), CC[A, B]] = empty[A, B]
}

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

4.视图界定

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

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

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

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

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

  class person(val age: Int) {
    def getAge: Int = age
  }

  implicit def toComparable(p: person): Comparable[person] = new Comparable[person] {
    override def compareTo(o: person): Int = {
      if (p.getAge > o.getAge) 1
      else 0
    }
  }

测试一下都是ok的:

    val p1 = new person(10)
    val p2 = new person(20)

    val compare = new Compare(p1,p2)
    val compareImplicit = new CompareImplicit(p1,p2)
    println(compare.compare().getAge)
    println(compareImplicit.compare().getAge)

5.上下文界定

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

  class Compare[T: Ordering] {
    def compareImplicit(first: T, second: T)(implicit ord:T => Ordered[T]):T = {
      if (first > second) first
      else second
    }

    def compareImplicitly(first: T, second: T):T = {
      val order = implicitly[Ordering[T]]
      if (order.gt(first, second)) first
      else second
    }
  }

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

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

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

implicit val order = new PersonOrdering

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

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

6.裂变与逆变

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

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

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

  class Tool() {
    def driver(): Unit = println("Tool Using")
  }

  class Vehicle() extends Tool {
    override def driver(): Unit = println("Driving")
  }

  class Car extends Vehicle {
    override def driver(): Unit = println("Car Driving")
  }
  
  class UsingTool[+T](t: T){}
  class TakeVehicle[-T](t: T){}

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

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

7.多重界定

   T <: A with B
   => A和B为T上界

   T >: A with B
   => A和B为T下界

   T >: A <: B
   => A为上届B为下界且A为B的子类,类似于继承关系判断

   T:A:B
   => 类型变量界定,即同时满足A[T]这种隐式值和B[T]这种隐式值
   
   T <% A <% B
   => 视图界定,即同时能够满足隐式转换的A和隐式转换的B

8.ClassTag

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

  def apply[T](runtimeClass1: jClass[_]): ClassTag[T] =
    runtimeClass1 match {
      case java.lang.Byte.TYPE      => ClassTag.Byte.asInstanceOf[ClassTag[T]]
      case java.lang.Short.TYPE     => ClassTag.Short.asInstanceOf[ClassTag[T]]
      case java.lang.Character.TYPE => ClassTag.Char.asInstanceOf[ClassTag[T]]
      case java.lang.Integer.TYPE   => ClassTag.Int.asInstanceOf[ClassTag[T]]
      case java.lang.Long.TYPE      => ClassTag.Long.asInstanceOf[ClassTag[T]]
      case java.lang.Float.TYPE     => ClassTag.Float.asInstanceOf[ClassTag[T]]
      case java.lang.Double.TYPE    => ClassTag.Double.asInstanceOf[ClassTag[T]]
      case java.lang.Boolean.TYPE   => ClassTag.Boolean.asInstanceOf[ClassTag[T]]
      case java.lang.Void.TYPE      => ClassTag.Unit.asInstanceOf[ClassTag[T]]
      case ObjectTYPE               => ClassTag.Object.asInstanceOf[ClassTag[T]]
      case NothingTYPE              => ClassTag.Nothing.asInstanceOf[ClassTag[T]]
      case NullTYPE                 => ClassTag.Null.asInstanceOf[ClassTag[T]]
      case _                        => new ClassTag[T]{ def runtimeClass = runtimeClass1 }
    }

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

    def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*)
    val numArr = mkArray(42, 13)
    println(numArr.map(_ + 3).mkString(" "))

    val strArr = mkArray("Japan","Brazil","Germany")
    println(strArr.map(_ + "ss").mkString(" "))

    val personArr = mkArray(p1,p2)
    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: _*)
    val numArrWithManifest = mkArrayWithManifest(42, 13)
    println(numArrWithManifest.map(_ + 3).mkString(" "))

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

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

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

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

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

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

10.类型约束

  // A =:= B   // 表示A类型等同于B类型  
  // A <:< B   // 表示A类型是B类型的子类 
  class animal extends java.io.Serializable {}

  def checkIsSerialzable[T](t: T)(implicit obj: T <:< java.io.Serializable): Unit = {
    println("true")
  }

可以调用:

    checkIsSerialzable(new animal())

不可以调用:

    checkIsSerialzable(new person(10))

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BIT_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值