泛型类
在类声明时,定义一些泛型类型,然后在类的内部,就可以使用这些泛型类型
在需要对类中的某些成员,如字段或方法中的参数进行统一的类型限制时,可以使用泛型类,使得程序具有更好的健壮性和稳定性
在使用类的时候,将类型参数替换为实际的类型即可
scala会自动推断泛型类型:给泛型类型的字段赋值时,scala会自动对类型进行推断
泛型函数:
与泛型类相似,在声明函数时指定泛型类型,然后在函数体内,多个变量或返回值,就可以使用泛型类型进行声明。
可以通过给使用了泛型类型的变量传递值,让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) } }
类型变量的边界(Bounds)
<: :指明上界,表达了泛型的类型必须是"某种类型"或某种类型的"子类"
'>: :指明下界,表达了泛型的类型必须是"某种类型"或某种类型的"父类"
/** * 这里的[T <: Comparable[T]] 表示类型T必须是Comparable[T]的子类 * 如果T为Comparable[T]的子类了,那么T一定会有compareTo这个方法,这是一个java的方法 */class Pair[T <: Comparable[T]](val first: T, val second: T) { def bigger = if(first.compareTo(second) > 0) first else second } object Type_Variable_Bounds { def main(args: Array[String]): Unit = { val pair = new Pair("Spark", "Hadoop") println(pair.bigger) //Spark } }
视图界定 View Bounds
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]
implicit ordered: Ordering[T]
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 } }
ClassTag和Manifest
上下文界定[T : ClassTag]:相当于动态类型,记录了当前T的类型,你使用时传入什么类型就是什么类型,在实际运行的时候我们获取T具体的类型
主要是应用于创建泛型数组,因为数组必须有具体的类型,否则无法创建相应的数组,利用[T : ClassTag]就可以创建成功
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 : 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]一个隐式值 这种方式是最常用的 主要是在运行时指定,在编译时无法确定的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" } }
Scala多重界定
T <: A with B:T是A或者B的子类
T >: A with B:A或者B是T的子类
T >: A <: B:T同时拥有下界A和上界B(也就是说A必为B的子类型,下界必须写在前面,上界必须写在后面)
T : A : B : 上下文界定
T <% A <% B:视图界定,T可以同时拥有多个视图界定,必须能够同时转化为A和B的要求
Scala类型约束
/** * A =:= B 表示A类型等同于B类型 * A <:< B 表示A类型是B类型的子类型 */object Type_Contraints { def main(args: Array[String]): Unit = { //隐式参数是从哪里传进来的?后面有一些列的判断 def rocky[T](i: T)(implicit ev: T <:< java.io.Serializable) { println(ev) //<function1> println("Life is short, you need Spark!") } rocky("spark") //rocky(100) error:Cannot prove that Int <:< java.io.Serializable. } }
总结
边界(Bounds)
[T <: Comparable[T]]
<: :指明上界,表达了泛型的类型必须是"某种类型"或某种类型的"子类"
'>: :指明下界,表达了泛型的类型必须是"某种类型"或某种类型的"父类"
视图界定 View Bounds
[T <% Comparable[T]]
<% : 对上边界的加强版,可以利用implicit隐式转换将实参类型转换成目标类型
上下文界定Context Bounds
[T : ClassTag]
[T : Manifest]
[T : Ordering]
说明存在一个隐式的值Ordering[T](implicit ordered: Ordering[T])
针对创建泛型数组的上下文界定:
作者:他与理想国
链接:https://www.jianshu.com/p/caca1ba8976e
共同学习,写下你的评论
评论加载中...
作者其他优质文章