scala笔记整理:类型参数(泛型)与隐士转换(代码片段)

author author     2022-11-05     640

关键词:

[TOC]


概述

类型参数是什么?类型参数其实就是Java中的泛型。大家对Java中的泛型应该有所了解,比如我们有List list = new ArrayList(),接着list.add(1),没问题,list.add("2"),然后我们list.get(1) == 2,对不对?肯定不对了,list.get(1)获取的其实是个String——"2",String——"2"怎么可能与一个Integer类型的2相等呢?

所以Java中提出了泛型的概念,其实也就是类型参数的概念,此时可以用泛型创建List,List list = new ArrayList[Integer](),那么,此时list.add(1)没问题,而list.add("2")呢?就不行了,因为类型泛型会限制传入的参数,只能往集合中list添加Integer类型,这样就避免了上述的数值的问题。

Scala中的类型参数和Java的泛型是一样的,也是定义一种类型参数。

最后,Scala类型参数也是Spark源码中非常常见的,因此同样必须掌握,才能看懂spark源码。

泛型类

Java 或 C++ 一样,类和特质可以带类型参数。在Scala中,我们用方括号类定义类型参数

class Student[T, S](val first: T, val second: S)

以上将定义一个带有2个类型参数T和S的类。在类的定义中,你可以用类型参数来定义变量,方法参数,以及返回值的类型。

我们把带有一个或者多个类型参数的类,叫作泛型类。如果你把类型参数替换成实际的类型,将得到一个普通的类。比如Student[Int,String]

Scala会从构造参数中推断出实际类型:

val p = new Student(42, "String")

你也可以自己指定类型,测试代码如下:

package cn.xpleaf.bigdata.p5.mygeneric

/**
  * scala的类型参数,即java中的泛型
  * 定义方式有异,java使用使用<>,scala使用[]
  * 泛型可以定义在类 特质 方法 函数
  *     泛型的作用,就是将运行期间的异常,提前到了编译器
  *     提高代码的通用性
  */
object _01GenericOps 
  def main(args: Array[String]): Unit = 
    genericOps1
  

  /**
    * 泛型类的定义
    */
  def genericOps1: Unit = 
    class Student[T, S](val first: T, val second: S) 
      println(first + "\t" + second)
    

    new Student(23, "xpleaf") // 可以做类型的自动推断
    new Student[Int, String](22, "jieling")
    new Student[Any, Any]("hadoop", "spark")
  

输出结果如下:

23  xpleaf
22  jieling
hadoop  spark

泛型函数

函数和方法也可以带有类型参数:

def getStudentInfo[T](stu: Array[T]) = stu(stu.length / 2)

和泛型类一样,你需要把类型参数放在方法名后面。

Scala会从调用该方法使用的实际类型来推断出类型:

def methodOps: Unit =
    def getStudentInfo[T](stu: Array[T]) = stu(stu.length / 2)

    val student = getStudentInfo(Array("garry", "tom", "john", "lucy", "Richard"))
    println(student)

在main函数中测试,输出结果如下:

john

类型变量界定—上限(upper bounds)

我们先看一个简单的实例,用于判断两个变量中较大的值,其中两个变量的类型均为Int型

/**
    * 类型变量界定
    */
def typeValueOps: Unit =
    class StudentInt(val first: Int, val second: Int) 
        def bigger = 
            if (first.compareTo(second) > 0) first else second
        
    

    val studentInt = new StudentInt(1, 2)
    println(studentInt.bigger)

上述StudentInt类中的bigger方法调用了compare方法,如果我们想比较两个String型的变量的大小,我们可以和上面一样,添加StudentStr类:

class StudentStr(val first: String, val second: String) 
    def bigger = 
        if (first.compareTo(second) > 0) first else second
    

如果我们针对每种基本类型都写一个具体的类,则代码量太大,同时也不够简洁,此时我们想到泛型能比较容易解决这个问题:

class Student[T](val first: T, val second: T) 
    def smaller = if (first.compareTo(second) < 0) first else second

然而与此同时,我们定义的泛型T并没有指定实现compareTo方法,也没有指定为某个类型的子类。在Java泛型里表示某个类型是Test类型的子类型,使用extends关键字:

<T extends Test>

//或用通配符的形式:
<? extends Test>

这种形式也叫upper bounds (上限或上界),同样的意思在Scala中的写法为:

[T <: Test] //或用通配符: [_ <: Test]

下面的代码结合了上限:

class Student[T <: Comparable[T]](val first: T, val second: T)
    def smaller = if (first.compareTo(second) < 0) first else second


val studentString = new Student[String]("limu","john")
println(studentString.smaller)

val studentInt = new Student[Integer](1,2)
println(studentInt.smaller)

注意,这相当于是对类型T加了一条限制:T必须是Comparable[T]的子类型。原来给T指定什么类型都可以,现在就不行了。

这样一来,我们可以实例化Student[String]。但是不能实例化Student[File],因为String是Comparable[String]的子类型,而File并没有实现Comparable[File]接口。

一个包含视图界定的完整案例如下:

/**
      * 泛型的上界
      * Upper Bound
      * [T <: 类] ---> [T <% 类] (视图的界定)
      */
def genericOps3: Unit = 
    class Student[T <% Comparable[T]](private val first: T, private val second: T) 
        def bigger():T = 
            /**
                  * 如果要让first和second有compareTo方法,必须要为Comparable的子类或者是Ordered的子类
                  * 说白了也就是要让这个类型参数T是Comparable或者Ordered的子类
                  * 一个类型是某一个类的子类,写法就要发生对应的变化
                  * java的写法:<T/? extends Comparable>
                  * scala的写法:[T <: Comparable]
                  */
            if(first.compareTo(second) > 0) 
                first
             else 
                second
            
        
    

    val stu = new Student[String]("xpleaf", "jieling")
    println(stu.bigger())
    val stu2 = new Student[String]("李四", "王五")
    println(stu2.bigger())
    /**
          * Error:(43, 13) type arguments [Int] do not conform to class Student‘s type parameter bounds [T <: Comparable[T]]
        val stu3 = new Student[Int](18, 19)
          说明Int不是Comparable的子类

          前面Int类型可以用,实际上是scala内部,将Int(隐士)转换为RichInt
          要想让该程序运行通过,就需要使用视图界定的方式
          [T <% Comparable[T]]
          使用这个%,其实就是强制指定将Int类型隐士转换为RichInt,而RichInt间接实现了Comparable
          */
    val stu3 = new Student[Int](18, 19)
    println(stu3.bigger())

在main函数中执行,输出结果如下:

xpleaf
王五
19

下限很少使用,所以这里就不进行说明了。

视图界定

其实上面已经有说明和应用,不过这里还是详细介绍一下。

刚才将的类型变量界定建立在类继承层次结构的基础上,但有时候这种限定不能满足实际要求,如果希望跨越类继承层次结构时,可以使用视图界定来实现的,其后面的原理是通过隐式转换(我们在下一讲中会详细讲解什么是隐式转换)来实现。视图界定利用<%符号来实现。

先看下面的一个例子:

class Student[T <: Comparable[T]](val first: T, val second: T) 
    def smaller = if (first.compareTo(second) < 0) first else second

val student = new Student[Int](4,2)
println(student.smaller)

可惜,如果我们尝试用Student(4,2)五实现,编译器会报错。因为Int和Integer不一样,Integer是包装类型,但是Scala的Int并没有实现Comparable。
不过RichInt实现了Comparable[Int],同时还有一个Int到RichInt的隐士转换。解决途径就是视图界定。

class Student[T <% Comparable[T]](val first: T, val second: T) 
    def smaller = if (first.compareTo(second) < 0) first else second

&lt;%关系意味着T可以被隐式转换成Comparable[Int]

个人理解:不管是类型变量界定还是视图界定,实际上都是在限制类型参数T,类型变量界定要求类型参数T必须是上界的子类或者是下界的父类;视图界定则是要求类型参数T必须能够隐式转换成“类似上界”的界定,比如上面提到的,Int隐式转换成RichInt,RichInt是Comparable[Int]的子类。这样看来,类型变量界定对类型参数的限制比视图界定对类型参数的限制是更大了。

协变和逆变

直接看下面的程序代码就能很容易理解:

package cn.xpleaf.bigdata.p5.mygeneric

/**
  * scala类型参数的协变和逆变
  *     scala默认不支持协变和逆变
  *         要想让scala的泛型支持协变,在泛型前面再加一个"+"
  *         要想让scala的泛型支持逆变,在泛型前面再加一个"-"
  *     但是一个类不能同时支持协变和逆变
  */
object _02GenericOps 
    def main(args: Array[String]): Unit = 
        /*
        val list:List[Person] = List[Person]()  // 正常的定义

        val list1:List[Person] = List[Student]()    // scala中的协变,java不支持
        // val list2:List[Teacher] = List[Person]()    // 逆变,java不支持,但是scala需要在定义泛型类的时候指定
        */

        val myList1:MyList[Person] = new MyList[Person]()
        val myList2:MyList[Person] = new MyList[Student]()

        val yourList1:YourList[Person] = new YourList[Person]()
        val yourList2:YourList[Student] = new YourList[Person]()
    

    class Person
    class Student extends Person
    class Teacher extends Person

    /**
      * 支持协变的泛型类
      */
    class MyList[+T] 

    

    /**
      * 支持逆变的泛型类
      */
    class YourList[-T] 

    

当然还有很多的理论知识和细节知识,但目前掌握这些就可以了。

类型通配符

1、类型通配符是指在使用时不具体指定它属于某个类,而是只知道其大致的类型范围,通过”_
<:” 达到类型通配的目的。

2、

def typeWildcard: Unit =
    class Person(val name:String)
        override def toString()=name
    
    class Student(name:String) extends Person(name)
    class Teacher(name:String) extends Person(name)
    class Pair[T](val first:T,val second:T)
        override def toString()="first:"+first+", second: "+second;
    
    //Pair的类型参数限定为[_<:Person],即输入的类为Person及其子类
    //类型通配符和一般的泛型定义不一样,泛型在类定义时使用,而类型通配符号在使用类时使用
    def makeFriends(p:Pair[_<:Person])=
        println(p.first +" is making friend with "+ p.second)
    
    makeFriends(new Pair(new Student("john"),new Teacher("摇摆少年梦")))

隐士转换

概述

1、在scala语言当中,隐式转换是一项强大的程序语言功能,它不仅能够简化程序设计,也能够使程序具有很强的灵活性。它们存在固有的隐式转换,不需要人工进行干预,例如Float在必要情况下自动转换为Double类型

2、在前一讲的视图界定中我们也提到,视图界定可以跨越类层次结构进行,它背后的实现原理就是隐式转换,例如Int类型会视图界定中会自动转换成RichInt,而RichInt实现了Comparable接口,当然这里面的隐式转换也是scala语言为我们设计好的 。

3、所谓隐士转换函数(implicit conversion function)指的是那种以implicit关键字声明的带有单个参数的函数。正如它的名称所表达的,这样的函数将自动应用,将值从一种类型转换成另一种类型。

Doube进行到Int的转换:

val x:Int = 3.5
implicit def double2Int(x:Double)=x.toInt
def conversionFunc: Unit =
    //Doube进行到Int的转换
    val x:Int = 3.5
    println("x===> " + x)

1、隐式函数的名称对结构没有影响,即implicitdefdouble2Int(x:Double)=x.toInt函数可以是任何名字,只不过采用source2Target这种方式函数的意思比较明确,阅读代码的人可以见名知义,增加代码的可读性。

2、Scala并不是第一个允许程序员提供自动类型转换的语言。不过,Scala给了程序员相当大的控制权在什么时候应用这些模块。

利用隐士函数丰富现在类库的功能

隐式转换功能十分强大,可以快速地扩展现有类库的功能.

import java.io.File
import scala.io.Source

//RichFile类中定义了Read方法
class RichFile(val file:File)
    def read = Source.fromFile(file).getLines().mkString


//隐式函数将java.io.File隐式转换为RichFile类
implicit def file2RichFile(file:File) = new RichFile(file)
val f = new File("E:/test/scala/wordcount.txt").read
println(f)

Java.io.File本身并没有read方法。

引入隐士转换

1、Scala默认会考虑两种隐式转换,一种是源类型,或者目标类型的伴生对象内的隐式转换函数;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换函数。

2、如果隐式转换不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import student._。

通常建议,仅仅在需要进行隐式转换的代码部分,比如某个函数或者方法内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。

隐士转换规则

1、隐式转换可以定义在目标文件当中(一个Scala文件中)

//转换函数
implicit def double2Int(x:Double)=x.toInt
val x:Int = 3.5

2、隐式转换函数与目标代码在同一个文件当中,也可以将隐式转换集中放置在某个包中,在使用进直接将该包引入即可

//在com.sparkstudy.scala.demo包中定义了子包implicitConversion
//然后在object ImplicitConversion中定义所有的引式转换方法
package implicitConversion
    object ImplicitConversion
        implicit def double2Int(x:Double)=x.toInt
        implicit def file2RichFile(file:File) = new RichFile(file)
    

class RichFile(val file:File)
    def read=Source.fromFile(file).getLines().mkString


//隐士转换规则
def implicitConversionRuleOps: Unit =
    //在使用时引入所有的隐式方法
    import com.sparkstudy.scala.demo.implicitConversion.ImplicitConversion._
    var x:Int=3.5
    println("x===> " + x)
    val f=new File("E:/test/scala/wordcount.txt").read
    println(f)

这种方式在scala语言中比较常见,在前面我们也提到,scala会默认帮我们引用Predef对象中所有的方法,Predef中定义了很多隐式转换函数

隐士转换发生的时机

1、当方法中参数的类型与实际类型不一致时

def f(x:Int)=x
//方法中输入的参数类型与实际类型不一致,此时会发生隐式转换
//double类型会转换为Int类型,再进行方法的执行
f(3.14)

2、当调用类中不存在的方法或成员时,会自动将对象进行隐式转换

我们上面进行的那个案例(File本身是没有read方法)

隐士参数

1、所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象,即隐式值,并注入参数。

2、Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值

//学生毕业报告
class StudentSubmitReport 
    def writeReport(ctent: String) = println(ctent)


implicit val stuentSign = new StudentSubmitReport

def signForReport(name: String) (implicit studentSReport: StudentSubmitReport) 
    studentSReport.writeReport(name + "come to here")

signForReport ("jack")

完整案例

ImplicitUtil

package cn.xpleaf.bigdata.p5

import java.io.File

import scala.io.Source

object ImplicitUtil 

    implicit def double2Int(d: Double): Int = d.toInt

    implicit def str2Int(str: String): Int = str.length

    implicit def file2RichFile(file: File) = new RichFile(file)

    implicit val swr:StudentWriteReport = new StudentWriteReport()


class RichFile(file: File) 
    def read() = Source.fromFile(file).getLines().mkString


class StudentWriteReport 
    def writeReport(content:String) = println(content)

implicitOps

package cn.xpleaf.bigdata.p5.implicitz

/**
  * scala隐士转换操作
  *     将一种类型,转化为另外的一种类型,这完成这一操作的背后就是隐士转换函数
  *     所谓隐士转换函数,其实就是在普通函数前面加上一个关键字——implicit
  *
  *     隐士转换函数的导入:
  *         1、如果隐士转换函数和调用它的操作,在同一个文件中,我们不要做任何操作
  *         2、如果不在一个文件中,需要收到导入,和导包是一样,唯一需要注意最后以._结尾,表导入该类中的所有的隐士转换函数
  *
  */
import java.io.File

import cn.xpleaf.bigdata.p5.ImplicitUtil._
import cn.xpleaf.bigdata.p5.StudentWriteReport

import scala.io.Source
object implicitOps 
    def main(args: Array[String]): Unit = 
        //        implicitOps1
        //        implicitOps2
        implicitOps3
    

    /**
      * 隐士转换参数
      * 其实就非常类似于之前学习过的柯里化
      */
    def implicitOps3: Unit = 
        /*  // 传统操作方式
        def signReport(name:String, swr:StudentWriteReport): Unit = 
            swr.writeReport(name)
        

        signReport("张三", new StudentWriteReport())*/

        def signForReport(name:String)(implicit swr:StudentWriteReport): Unit = 
            swr.writeReport(name)
        

        signForReport("张三")
    

    /*
    class StudentWriteReport 
        def writeReport(content:String) = println(content)
    

    implicit val swr:StudentWriteReport = new StudentWriteReport()
    */

    /**
      * 使用隐士转换丰富现在类型的API
      */
    def implicitOps2: Unit =

        var file = new File("/Users/yeyonghao/test.txt")

        var lines = file.read()

        println(lines)
    

    /**
      * 隐士转换操作
      */
    def implicitOps1: Unit = 
        val x:Int = 3
        val y:Int = 3.5
        val z:Int = "klkelfldlkfj"

        println("x=" + x)
        println("y=" + y)
        println("z=" + z)

    

scala学习笔记-隐式转换与隐式参数(18)

Scala提供的隐式转换和隐式参数功能,是非常有特色的功能。是Java等编程语言所没有的功能。它可以允许你手动指定,将某种类型的对象转换成其他类型的对象。通过这些功能,可以实现非常强大,而且特殊的功能。Scala的隐式... 查看详情

在 Scala 中,泛型类型参数可以与 *function* 定义一起使用吗?

】在Scala中,泛型类型参数可以与*function*定义一起使用吗?【英文标题】:InScala,cangenerictypeparametersbeusedwith*function*definitions?【发布时间】:2013-03-0706:47:39【问题描述】:是否有允许函数字面量上的泛型类型参数的语法?我知道... 查看详情

scala学习笔记之隐式参数和隐式转换并用

隐式转换条件:1.当表达式类型与预期的类型不同时2.当对象访问一个不存在的成员时3.当对象调用某个方法,而该方法的参数声明与传入参数不相匹时。隐式转换搜索范围:1.位于源火目标类型伴生对象中的隐式函数。2.位于当... 查看详情

java泛型与集合笔记

第一章Java的泛型为了兼容性和防止代码爆炸,在编译成字节碼时会进行类型擦除,编译器自动添加代码做类型转换(用到List<Integer>的地方用Integer来做转换),自动做装箱拆箱,做foreach替换,在多个参数的情况下自动打包... 查看详情

spark基础-scala学习(七类型参数)(代码片段)

类型参数是什么类似于java泛型,泛型类泛型函数上边界Bounds下边界ViewBoundsContextBoundsManifestContextBounds协变和逆变ExistentialType泛型类scala>:paste//Enteringpastemode(ctrl-Dtofinish)classStudent[T](vallocalId:T)defgetSchoolId(hukouId:T)="S-"+hukouId+"-"+localId... 查看详情

scala入门系列(十三):类型参数

引言Scala中类型参数是什么呢?其实就类似于Java中的泛型。定义一种类型参数,比如在集合、类、函数中定义类型参数,然后就可以保证使用到该类型参数的地方就只能是这种类型,从而实现程序更好的健壮性。 泛型类泛... 查看详情

scala之类型参数和对象

泛型类型边界视图界定逆变和协变上下文界定源代码1.泛型泛型用于指定方法或类可以接受任意类型参数,参数在实际使用时才被确定,泛型可以有效地增强程序的适用性,使用泛型可以使得类或方法具有更强的通用性。在Scala... 查看详情

java实训笔记——-抽象类-接口-泛型-集合

1.1方法的可变参数从JDK1.5之后,定义方法时参数的个数可以变化语法:最后一个数据类型后增加3个点注意:1.可变参数只能处于参数列表的最后;2.一个方法中最多只能包含一个可变参数;3.可变参数的本质就是一个数组,因此... 查看详情

Scala:从泛型类型到第二泛型类型的隐式转换

】Scala:从泛型类型到第二泛型类型的隐式转换【英文标题】:Scala:ImplicitConversionFromGenericTypetoSecondGenericType【发布时间】:2013-11-1514:09:59【问题描述】:假设我有两组类,第一组继承自Foo,第二组继承自Bar。classFooclassBazextendsFooc... 查看详情

泛型技巧系列:如何提供类型参数之间的转换

...”类文章也见不到的。我考虑把其中概念性的部分系统地整理成书,而技巧性的东西则通过我这个系列不定期地分享到Blog上。希望用到.NET泛型的人能从我这些技巧中受益。首先我要介绍的技巧是如何提供类型参数之间的转换。... 查看详情

大数据学习之scala语言的高级特性42

1、什么是泛型类和Java或者C++一样,类和特质可以带类型参数。在Scala中,使用方括号来定义类型参数测试程序: 2、什么是泛型函数函数和方法也可以带类型参数。和泛型类一样,我们需要把类型参数放在方法名之后。注意... 查看详情

读书笔记c#type类型与泛型有关的某些属性浅析

IsGenericType如果类型为泛型,则返回true。GetGenericArguments返回Type对象数组,这些对象表示为构造类型提供的类型变量,或泛型类型定义的类型参数。如果是MyList<int,Person>,则返回int和Person类型的数组,如同Type[]tpyes={typeof(int),t... 查看详情

读编程与类型系统笔记09_泛型数据结构

1. 恒等函数1.1. 在代数中,恒等函数指的是函数f(x)=x1.2. 恒等逻辑与getNumbers()和assembleWidgets()的问题域解耦,因为恒等逻辑和问题域是正交的,或者说是独立的2. 类型参数2.1. 将不同函数的区别,即它们的实参类... 查看详情

scala泛型[t]的使用(代码片段)

...basics/***1,scala的类和方法、函数都可以是泛型。**2,关于对类型边界的限定分为上边界和下边界(对类进行限制)*上边界:表达了泛型的类型必须是"某种类型"或某种类型的"子类",语法为“<:”,*下边界:表达了泛型的类型必须... 查看详情

java泛型相关整理

1.概述Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型,即参数化类型。将类型由原来的具体的类型(类似于方法的变量参数,该变量定义... 查看详情

scala的泛型(代码片段)

...泛型 泛型介绍:泛型用于指定方法或类可以接受任意类型参数,参数在实际使用时才被确定,泛型可以有效地增强程序的适用性,使用泛型可以使得类或方法具有更强的通用性。泛型的典型应用场景是集合及集合中的方法参... 查看详情

泛型学习笔记

定义:参数化类型。使程序具有更好的可读性和安全性。优点:(泛型类,比如用于集合等容器类型时)1.编译器进行类型检查,避免插入错误类型的对象;2.输出时不需要类型的强制转换。 1.泛型类可以有多个类型变量。例... 查看详情

Scala - 从泛型类型获取类对象

】Scala-从泛型类型获取类对象【英文标题】:Scala-obtainingaclassobjectfromagenerictype【发布时间】:2011-11-2106:43:43【问题描述】:是否可以纯粹从泛型参数创建一个Class对象?例如:classmyclass[T]defsomething():Class[_<:T]=classOf[T]//thisdoesn\'t... 查看详情