Scala 泛型以及泛型约束

泛型类

  • 在类声明时,定义一些泛型类型,然后在类的内部,就可以使用这些泛型类型
  • 在需要对类中的某些成员,如字段或方法中的参数进行统一的类型限制时,可以使用泛型类,使得程序具有更好的健壮性和稳定性
  • 在使用类的时候,将类型参数替换为实际的类型即可
  • 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 : Ordering]
    • 说明存在一个隐式的值Ordering[T](implicit ordered: Ordering[T])
    • 针对创建泛型数组的上下文界定:
      • [T : ClassTag]
      • [T : Manifest]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容