一文详解scala泛型及类型限定

今天知识星球球友,微信问浪尖了一个spark源码阅读中的类型限定问题。这个在spark源码很多处出现,所以今天浪尖就整理一下scala类型限定的内容。希望对大家有帮助。

scala类型参数要点

1. 非变

trait Queue[T] {} 这是非变情况。这种情况下,当类型S是类型A的子类型,则Queue[S]不可认为是Queue[A]的子类型或父类型,这种情况是和Java一样的。 2. 协变 trait Queue[+T] {} 这是协变情况。这种情况下,当类型S是类型A的子类型,则Queue[S]也可以认为是Queue[A}的子类型,即Queue[S]可以泛化为Queue[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变。 3. 逆变 trait Queue[-T] {} 这是逆变情况。这种情况下,当类型S是类型A的子类型,则Queue[A]反过来可以认为是Queue[S}的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变。

4. 类型下界

U >: T

这是类型下界的定义,也就是U必须是类型T的父类(或本身,自己也可以认为是自己的父类)。

5. 类型上届

S <: T

这是类型上界的定义,也就是S必须是类型T的子类(或本身,自己也可以认为是自己的子类)。

泛型与约束实战

1 泛型函数

ClassTag[T]保存了泛型擦除后的原始类型T,提供给被运行时的。

/*
     *  泛型[],中括号F、S、T都表示运行时参数类型,
     * ClassTag[T]保存了泛型擦除后的原始类型T,提供给被运行时的。
     */
    class Triple[F: ClassTag, S, T](val first: F, val second: S, val third: T)
    
    object HelloTypeParam {
      def main(args: Array[String]): Unit = {
    
        // 运行执行代码:val triple: Triple[String, Int, Double]
        val triple = new Triple("Spark", 3, 3.1415)
        
        // 运行执行代码:val bigData: Triple[String, String, Char]
        val bigData = new Triple[String, String, Char]("Spark", "Hadoop", 'R');
        
        // getData函数传入泛型为T的运行时List类型参数,返回list.length / 2的整数。
        def getData[T](list:List[T]) = list(list.length / 2)
        // List索引从0开始,执行结果:Hadoop
        println(getData(List("Spark","Hadoop",'R')));
        
        // 获得getData函数引用
        val f = getData[Int] _
        // 调用getData函数,执行结果:4
        println(f(List(1,2,3,4,5,6)));
        
      }
    }

2 类型变量界定

泛型参数类型限定,限定具体类的可以调用特定的方法。

/*
     * <:泛型类型限定符,表示只限定Comparable子类
     * Comparable[T]:为T下界,T:为Comparable[T]上界
     */
    class Pair[T <: Comparable[T]](val first: T, val second: T) {
      // compareTo方法进行比较,如果大于0返回first
      def bigger = if (first.compareTo(second) > 0) first else second
    }
    
    
    // 声明带T泛型参数的类
    class Pair_Lower_Bound[T](val first: T, val second: T) {
        // 传入的参数泛型T 必须为 R的父类(超类),返回构造Pair_Lower_Bound对象 
        // R:为T的上界,T:为R下界  
        def replaceFirst[R>:T](newFirst:R) = new Pair_Lower_Bound[R](newFirst,second)
    }
    
    object TypeVariablesBounds {
      def main(args: Array[String]): Unit = {
        // 函数调用
        var pair = new Pair("Spark", "Hadoop")
        // 执行结果:Spark
        println(pair.bigger)
      }
    }

3 泛型视图限定

泛型视图限定:表示把传入不是Comparable[T]类型的隐式传换为Comparable[T]类型,Comparable[T]:为T下界,T:为Comparable[T]上界。

/*
     * <%泛型视图限定符,表示把传入不是Comparable[T]类型的 隐式传换 为Comparable[T]类型
     * Comparable[T]:为T下界,T:为Comparable[T]上界
     */
    class PairNotPerfect[T <% Comparable[T]](val first: T, val second: T) {
      // compareTo方法进行比较,如果大于0返回first
      def bigger = if (first.compareTo(second) > 0) first else second
    }
    
    /*
     * <%泛型视图限定符,表示把传入不是Ordered[T]类型的 隐式传换 为Ordered[T]类型
     * Ordered[T]:为T下界,T:为Ordered[T]上界
     * Ordered继承: extends Any with java.lang.Comparable[A]
     */
    class PairBetter[T <% Ordered[T]](val first: T, val second: T) {
      def bigger = if (first.compareTo(second) > 0) first else second
    }
    
    object ViewVariablesBounds {
      def main(args: Array[String]): Unit = {
        // 函数调用
        var pair = new PairNotPerfect("Spark", "Hadoop");
        // 执行结果:Spark
        println(pair.bigger)
        
        // 函数调用,Int类型进行隐式转换,将Int -> RichInt,RichInt实现了Comparable接口
        var pairInt = new PairNotPerfect(3,5)
        // 执行结果:5
        println(pairInt.bigger);
        
        // 函数调用,Int类型进行隐式转换,将String -> RichString,RichString实现了Comparable接口
        var pairBetterStr = new PairBetter("Java","Scala");
        println(pairBetterStr.bigger);
        
        // 函数调用
        var pairBetterInt = new PairBetter(20, 12);
        // 执行结果:Spark
        println(pairBetterInt.bigger)
      }
    }

4 上下文界定

上下文界定:上下文界定是隐式参数的语法糖。如:Ordering:可以进行隐式转化的T类型。

class PairOrdering[T: Ordering](val first: T, val second: T) {
      // compareTo方法进行比较,如果大于0返回first
      def bigger(implicit ordered: Ordering[T]) = if (ordered.compare(first, second) > 0) first else second
    }
    
    object ContextBounds {
      def main(args: Array[String]): Unit = {
        // 函数调用
        var pair = new PairOrdering("Spark", "Hadoop")
        // 执行结果:Spark
        println(pair.bigger)
      }
    }

5 Manifest关键字

Manifest关键字:数组在声明时必须要求指定具体的类型,在函数泛型是无法知道具体类型,通过Manifest关键字使得运行时可以根据这个Manifest参数做更多的事情。

def main(args: Array[String]): Unit = {
    /*
     *  定义方法array
     *  Manifest:需要运行时存储T的实际类型,运行时是做为参数运行在方法的上下文中。
     *  数组在定义时必须知道具体的类型,所以在声明方法时,需要添加Manifest
     */
    def arrayMake[T: Manifest](first: T, second: T) = {
      val r = new Array[T](2);
      r(0) = first;
      r(1) = second;
      // 返回r
      r;
    }
 
    /*
     *  执行结果:
     *  1
     *  2
     */
    arrayMake(1,2).foreach(println)
  }

6 ClassTag关键字

ClassTag[T]保存了泛型擦除后的原始类型T,提供给被运行时的。

def main(args: Array[String]): Unit = { 
     
        /*
         * ClassTag:在运行时指定,在编译时无法确定的
         */
        def mkArray[T:ClassTag](elems:T*) = Array[T](elems:_*)
        
        /*
         *  执行结果:
         *  42
         *  13
         */
        mkArray(42,13).foreach(println)
        
        /*
         * 执行结果:
         * Japan
         * Brazil
         * Germany
         */
        mkArray("Japan","Brazil","Germany").foreach(println)
    }

7 ClassManifest关键字

在引入Manifest的时候,还引入了一个更弱一点的ClassManifest,所谓的弱是指类型信息不如Manifest那么完整。用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路径依赖类型中,Manifest存在问题。

class A[T]
    val m = manifest[A[String]]
    // 执行结果:com.scala.type_param.Manifest_ClassTag$A$1[java.lang.String]
    println(m);
    val cm = classManifest[A[String]]
    // 执行结果:com.scala.type_param.Manifest_ClassTag$A$1[java.lang.String]
    println(cm);

8 多重界定符

/*
      // 表示:A和B为T上界
      T <: A with B
      
      // 表示:A和B为T下界
      T >: A with B
      
      // 表示:同时拥有上界和下界,并且A为下界,B为上界,A为B的子类,顺序不能颠倒。
      T >: A <: B
      
      // 表示:类型变量界定,即同时满足AT这种隐式值和BT这种隐式值
      T:A:B
      
      // 表示:视图界定,即同时能够满足隐式转换的A和隐式转换的B
      T <% A <% B 
    */

9 Scala类型约束

def main(args: Array[String]): Unit = {
      // A =:=B // 表示A类型等同于B类型
      // A <:<B  // 表示A类型是B类型的子类
      def rocky[T](i: T)(implicit ev: T <:< java.io.Serializable) {
        // 执行结果:Life is short ,you need spark!!!
        println("Life is short ,you need spark!!!")
      }
      rocky("Spark")
    }

本文整理自网络,若有侵权,请联系微信158570986删除。

原文发布于微信公众号 - Spark学习技巧(bigdatatip)

原文发表时间:2018-08-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏mathor

ST算法

 RMQ(Range Minimum/Maximum Query),即区间最值查询。对于长度为n的数列arr,回答若干询问Q(i,j),返回数列arr中下标在i...

2172
来自专栏WD学习记录

Leetcode Regular Expression Matching

Given an input string (s) and a pattern (p), implement regular expression matchi...

922
来自专栏技术博客

C#基础知识系列十(集合)

  本节主要是来了解学习集合,以方便在程序编写时,什么地方该选用什么集合,让程序更健壮的运行起来。在学习了解集合之前,首先需要了解一些数据结构方面的知识。下面我...

1233
来自专栏HansBug's Lab

1798: [Ahoi2009]Seq 维护序列seq

1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec  Memory Limit: 64 MB Submit: 2930...

3015
来自专栏黑泽君的专栏

java基础学习_基础语法(上)01_day02总结

=============================================================================

1153
来自专栏函数式编程语言及工具

Scalaz(12)- Monad:再述述flatMap,顺便了解MonadPlus

  在前面的几篇讨论里我们初步对FP有了些少了解:FP嘛,不就是F[A]吗?也是,FP就是在F[]壳子(context)内对程序的状态进行更改,也就是在F壳子(...

1957
来自专栏jessetalks

Javascript基础回顾 之(一) 类型

  本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Jav...

3777
来自专栏PHP在线

php的字符串常用函数

1. str_word_count 统计单词个数 2. count_chars 得到字符串里面字符的有关情况 3. str_len 得到字符串长度,就是...

4496
来自专栏趣谈编程

Unicode与UTF-8的区别

要弄清Unicode与UTF-8的关系,我们还得从他们的来源说起,下来我们从刚开始的编码说起,直到Unicode的出现,我们就会感觉到他们之间的关系

1292
来自专栏函数式编程语言及工具

泛函编程(1)-泛函编程是如何实现的

  泛函编程就是把函数组合起来形成一个完整的程序。可想而知,函数组合的过程可以是曲折的,形成的程序可以是复杂的。那么泛函编程又是如何保证一个复杂的函数组合程序是...

2318

扫码关注云+社区

领取腾讯云代金券