Scala中的泛型详解

Scala中的泛型详解

  1. 类型参数可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法
  2. 类型参数
    1. 调用时不指定[T]:可以通过给泛型声明的变量传递值来让scala自动推断泛型的实际类型;返回的是使表达式编译通过的合适的类型;在编译时不会检查类型是否满足
    2. 调用时指定[T]:可以在函数的调用时候指定泛型的类型;则返回对就必须是T类型;会在编译时检查类型,不满足泛型规则编译不通过

泛型类

  • 在类声明时,定义一些泛型类型,然后在类的内部,就可以使用这些泛型类型
  • 在需要对类中的某些成员,如字段或方法中的参数进行统一的类型限制时,可以使用泛型类,使得程序具有更好的健壮性和稳定性
  • 在使用类的时候,将类型参数替换为实际的类型即可
  • scala会自动推断泛型类型:给泛型类型的字段赋值时,scala会自动对类型进行推断
object test{
  class Stack[A] {
    private var elements: List[A] = Nil
    def push(x: A) { elements = x :: elements }
    def peek: A = elements.head
    def pop(): A = {
      val currentTop = peek
      elements = elements.tail
      currentTop
    }
  }
  // Stack 类的实现中接受类型参数 A。 这表示其内部的列表,var elements: List[A] = Nil,只能够存储类型 A 的元素。
  //方法 def push 只接受类型 A 的实例对象作为参数
  def main(args: Array[String]): Unit = {
    val stack = new Stack[Int]
    stack.push(1)
    stack.push(2)
    println(stack.pop)  // prints 2
    println(stack.pop)  // prints 1
  }
}

泛型函数:

  • 与泛型类相似,在声明函数时指定泛型类型,然后在函数体内,多个变量或返回值,就可以使用泛型类型进行声明。
  • 可以通过给使用了泛型类型的变量传递值,让scala自动推断泛型的实际类型,也可以在调用函数的时候,手动指定泛型的实际类型
class Triple[X, Y, Z](val first: X, val second: Y, val thrid: Z)
 
object Hello_Type_Parameterization {
    def main(args: Array[String]): Unit = {
        //在定义后scala的类型推断会得出triple类型为 Triple[String, Int, Double]
        val triple = new Triple("Spark", 3, 3.1415926)
        //显示声明类型
        val bigData = new Triple[String, String, Char]("Spark", "Hadoop", 'R')
    
    
        //定义泛型函数
        def getData[T](list: List[T]) = list(list.length / 2)
        println(getData(List("Spark", "Hadoop", 'R')))  //Hadoop
        //显式指定类型
        val f = getData[Int] _      //val f: List[Int] => Int
        println(f(List(1,2,3,4,5,6,7,8))) //5
        
        //定义参数也存在上下文的约束
        def foo[A, B](f: A => List[A], b: A) = f(b)
    }
}

类型变量的上边界

  1. 为什么需要边界:在指定泛型类型的时候,有时,我们需要对泛型的类型的范围进行界定,而不是任意的类型

  2. 上边界:[A<B]

  3. 上边界特性:左边的类型参数A是右边类型B的子类

  4. 作用:我们可能要求某个泛型,它就必须是某个类A的子类,这样在程序中就可以放心地调用A类的方法

    • 比如:我们并不知道类型T到底有没有compareTo方法,编译报错,所以要使用上边界,是参数类型T的类型是含有compareTo的类或者其子类
    1. 编译报错:class Pair[T] (val first:T, val second:T){def smaller = if (first.compareTo(second)) }
    2. 编译正确:class Pair[T <: Comparable[T]] (val first:T, val second:T) {def smaller = if (first.compareTo(second)) }
object test3{
  class Person(val name: String){}
  class Teacher(name:String)
  class Student(name: String) extends Person(name)
  class Play[T <: Person](p: T) {}


  def main(args: Array[String]): Unit = {
    val wiki=new  Student("Wiki")
    val tom=new Teacher("Tom")
    val play =new Play(wiki) //[Person]可以省略自行推导
    //   val s=new Play(tom) 报错 因为Teacher不是Person子类。这就是上边界
  }
}

泛型变量的下边界

  • 下边界:[A>B]

  • 下边界特性:左边的类型参数A是右边类型B父类

    注意:如果是在调用的时候省略了[T],让scala自动去推断,scala会自动向上转型,使编译通过而不会报错(无论参数是什么类型都会编译通过)

sobject test4{
  class Father(val name: String)
  class Child(name: String) extends Father(name)
  class Grandson(name: String) extends Child(name)
  class Frind(val name: String)
  class makyFrind[R >: Child](name:R)
  def main(args: Array[String]): Unit = {
    val father=new Father("wiki")
    val child=new Child("wiki")
    val grandson =new Grandson("wiki")
    val frind =new Frind("wiki")
    val makefrind1: makyFrind[Father] = new makyFrind(father)
    val makefrind2: makyFrind[Child] = new makyFrind(child)
    val makefrind3: makyFrind[Object] = new makyFrind(frind)   //不会报错 会自动向上转型
    val makefrind = new makyFrind[Frind](frind) // 编译报错   frind不是child的父类
    val makefrind4: makyFrind[Child] = new makyFrind(grandson)
  }
}

视图界定

  • view bounds其实就是bounds 上边界的加强版本,对bounds的补充 <变成<%
  • 可以利用implicit隐式转换将实参类型转换成目标类型
/**
当给下面这个类传入3、5时会报错。因为3、5不是Comparable[T]的子类
class Pair_NotPerfect[T <: Comparable[T]](val first: T, val second: T) {
    def bigger = if (first.compareTo(second) > 0) first else second 
*/
 
/*
 *将<:改成 <%就是视图界定 这样就可以传递3和5了,就不会报错了
 *我们可以把传入的T类型的实例隐式的转换成Comparable[T]类型
*/
class Pair_NotPerfect[T <% Comparable[T]](val first: T, val second: T) {
    def bigger = if (first.compareTo(second) > 0) first else second 
}
 
Ordered视图界定
/**
 * 
 *上面这种方式的12行first.compareTo(second) > 0 通过compareTo来比较 但是不能直观的像数学比较那样清晰
 * Scala提供了Ordered视图界定,Ordered在Comparable上提供一些关系型的操作符 < > <= >=等
 * 
 */
class Pair_Batter[T <% Ordered[T]](val first: T, val second: T) {
    //这里的 > 是因为Ordered中提供的方法
    def bigger = if (first > second) first else second 
}
 
 

object View_Bounds {
    def main(args: Array[String]): Unit = {
        var pair = new Pair_NotPerfect("Spark", "Hadoop")
        println(pair.bigger)  //Spark
 
        /*
         * 当类型界定为Pair_NotPerfect[T <: Comparable[T]]报错 因为Int本身不是Comparable的子类
         * 
         * 当类型界定为视图界定时Pair_NotPerfect[T <% Comparable[T]] 就可以正常运行
         * 是因为Int本身不是Comparable的子类型 Scala通过"隐式转换"将Int转换成RichInt 而这个类型是Comparable的子类
         */
        var pairInt = new Pair_NotPerfect(3, 5)  //Int -> RichInt
        println(pairInt.bigger)  //5     
        
        /**
         * 注意:这样定义不是因为String的上界是Ordered[String]
         * 当使用视图界定时 会发生"隐式转换" 把String --> RichString
         * 而RichString是Ordered[RichString]的子类型  RichString中是实现了这样的 < > <= >=等方法
         * 从而真正是让String类型完成视图界定
         */
        var pair_Batter_String = new Pair_Batter("Java", "Scala")
        println(pair_Batter_String.bigger)  //Scala
        
        val pair_Batter_Int = new Pair_Batter(20, 12)
        println(pair_Batter_Int.bigger)     //20
    }
}

上下文界定Context Bounds

  • 上下文界定[T : Ordering],这种写法在Spark中是广泛使用的,说明存在一个隐式的值Ordering[T]
    • 一个上下文界定相当于一个隐式参数。([A:B]:就相当于一个隐式参数implicit b:B[A])
    • 如:def foo[A:B] = g(a) 等价于 def fooA(implicit b:B[A]) = g(a)
      1. 隐式参数的类型是B
      2. foo函数的泛型和隐式参数b的泛型是A
class Pair_Ordering[T : Ordering] (val first: T, val second: T) {
  
    //这是一个隐式转换的显式定义,这个函数没有参数,当时函数执行的时候 这个隐式值就会自动传进来
    def bigger(implicit ordered: Ordering[T]) = {
        if (ordered.compare(first, second) > 0) first else second
    }
}
 

object Context_Bounds {
    def main(args: Array[String]): Unit = {
        val pair = new Pair_Ordering("Spark", "Hadoop")
        println(pair.bigger)         //Spark
        
        val pairInt = new Pair_Ordering(3, 5)
        println(pairInt.bigger)      //5
    }
}
  • implicitly:用来获取上下文中满足类型要求的隐式值
object test6{
  class  Stringer[T] {
    def toString(a: T, b: T): Unit = {
      println(s"$a + $b")
    }
  }
  def foo1[T](a: T, b: T)(implicit stringer: Stringer[T])= {
    stringer.toString(a, b)
  }
  // 在方法入参上拿不到关于 Stringer 对象的值,那么我们可以通过 implicitly 这个 标识符 来获取程序上下文中存在的关于Stringer[T]类型的隐式值,
  // 这个 标识符 的作用就在于此,它是自动的去获取到。
  def foo2[T:Stringer](a: T, b: T) = {
    val stringer: Stringer[T] = implicitly[Stringer[T]]
    stringer.toString(a, b)
  }
  def main(args: Array[String]): Unit = {
    implicit val stringer: Stringer[Int] = new Stringer[Int]
    val result1 = foo1(2, 3)
    val result2 = foo2(2, 3)
  }
}

ClassTag和Manifest

  • 上下文界定[T : ClassTag]:相当于动态类型,记录了当前T的类型,你使用时传入什么类型就是什么类型,在实际运行的时候我们获取T具体的类型
  • 主要是应用于创建泛型数组,因为数组必须有具体的类型,否则无法创建相应的数组,利用[T : ClassTag]就可以创建成功

JVM中的泛型并不会保存泛型的,我们一般在Java开始时候涉及到的泛型都是源码级别的,当我们反编译打开编译之后的class文件会发现并不存在泛型信息。Scala为了在运行时能够获取到泛型信息,就推出了如上关键字。

首先先看一个在Java中由于运行期间无法获取泛型信息的例子:

class ArrayDemo<T> {
	public T[] arrays = new T[10]; //创建泛型数组不可以,编译不通过
	public T[] makeArray(int size) {
		return new T[size];//错误信息还是: 创建泛型数组
	}
}

在Scala中同样存在这样的问题:

class ScalaArrayDemo[T] {
	//Error: cannot find class tag for element type T
	def makeTArray(): Array[T] = new Array[T](10)
}

Scala为了解决这个问题就提出了Manifest、ClassManifest、ClassTag 、TypeTag关键字。

import scala.reflect.ClassTag
 
object Manifest_ClassTag {
    def main(args: Array[String]): Unit = {
        
        /**
         * Q: 可以创建泛型数组吗?
         *理论上是不可以的,因为没有指定具体的,在Scala程序运行中,数组必须有具体的类型,没有否无法创建的相应的数组
         *引出Manifest的概念可以创建泛型数组
         *[T : Manifest]这样的写法被称之为Manifest上下文界定  实质上这是需要一个Manifest[T]类型的隐式对象 这又是一个"隐式转换"的过程,有这样的一个隐式转换来辅助我们构建Manifest[T]来确定T的类型
         * 通过这个隐式的值来辅助构建泛型数组,来确定T的具体类型
         * 所以在创建泛型函数时 需要Manifest的类型来辅助构建泛型数组,借助Manifest类型对象来指定泛型数组具体的类型
         * 
         * 通过Manifest[T]可以记录T的类型 在实际运行的时候我们获取T具体的类型
         * */
      
      //下面的函数等效于def arrayMake[T](first:T, second:T)(implicit x: Manifest[T])
        def arrayMake[T : Manifest](first: T, second: T) = {
           val r = new Array[T](2) 
           r(0) = first
           r(1) = second
           r
        }
        arrayMake(1, 2).foreach(println)         //1 2
        
        /**
         * Manifest的原生写法  不推荐
         */
        def manif[T](x: List[T])(implicit m: Manifest[T]) = {
            if (m <:< manifest[String])          //<:< 表示 m是manifest[String]类型
              println("List Strings")
            else
              println("Some other type")
        }
        manif(List("Spark", "Hadoop"))   //List Strings
        manif(List(1, 2))                //Some other type
        manif(List("Scala", 3))          //Some other type
        

        /**
         * [T : ClassTag]这种写法说明:当这个函数在运行时时 对存在一个ClassTag[T]一个隐式值 这种方式是最常用的
         ClassManifest是Manifest的一个弱化版本,就是保存的类型信息不如Manifest多。不过scala在2.10里却用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路径依赖类型中,Manifest存在问题。
           * ClassTag比TypeTag弱,它只包含运行时class信息,TypeTag包含所有static类型信息
         主要是在运行时指定,在编译时无法确定的type的信息
         编写编译的时候没有具体类型,运行的时候必须要有具体的类型,所以需要一种机制运行的时候会根据类型进行推断类型,classTag会帮我们存储这个类的信息,然后交给虚拟机
         */
        def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*)
        mkArray(42, 13).foreach(println)                //42  13
        mkArray("Japan", "Brazil", "Germany").foreach(println)          //"Japan", "Brazil", "Germany"
        
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值