Scala入门到精通——第十七节 类型参数(一)

本节主要内容

  1. 类型变量界定(Type Variable Bound)
  2. 视图界定(View Bound)
  3. 上界(Upper Bound)与下界(Lower Bound)

1. 类型变量界定(Type Variable Bound)

类型变量界定是指在泛型的基础上,对泛型的范围进行进一步的界定,从而缩下泛型的具体范围,例如:
//下面的类编译通不过
//因为泛型T在编译的时候不能确定其具体类型
//即并不是所有的类中都存在compareTo方法
class TypeVariableBound {
  def compare[T](first:T,second:T)={
    if (first.compareTo(second)>0)
      first
    else
      second
  }
}

object TypeVariableBound{
  def main(args: Array[String]): Unit = {
      val tvb=new TypeVariableBound
      println(tvb.compare("A", "B"))
  }
}

如果在使TypeVariableBound类编译通过,此时可以利用类型变量界定对泛型T进行界定,指明所有的泛型T都实现了Comparable接口,代码如下:

class TypeVariableBound {
  //采用<:进行类型变量界定
  //该语法的意思是泛型T必须是实现了Comparable
  //接口的类型
  def compare[T <: Comparable[T]](first:T,second:T)={
    if (first.compareTo(second)>0)
      first
    else
      second
  }
}

object TypeVariableBound{
  def main(args: Array[String]): Unit = {
      val tvb=new TypeVariableBound
      //由于String类型实现了Comparable接口
      //下面这种使用方式是合法的
      println(tvb.compare("A", "B"))
   }
}

从上述代码可以看到,compare方法中如果输入的类型处于Comparable类对应继承层次结构中,则是合法的,否则的话编译会报错,例如:

//定义一个case class类Person
case class Person(var name:String,var age:Int)

class TypeVariableBound {
  def compare[T <: Comparable[T]](first:T,second:T)={
    if (first.compareTo(second)>0)
      first
    else
      second
  }
}

object TypeVariableBound{
  def main(args: Array[String]): Unit = {
      val tvb=new TypeVariableBound
      println(tvb.compare("A", "B"))
      //下面这一行代码会报错,这是因为Person类并没有
      //实现Comparable接口
      println(tvb.compare(Person("stephen",19), Person("john",20)))
  }
}

如果需要输入Person类也合法的话,则Person类要实现Comparable接口,代码如下:

//声明Person类为case class且实现了Comparable接口
case class Person(var name:String,var age:Int) extends Comparable[Person]{
  def compareTo(o:Person):Int={
    if (this.age>o.age) 1
    else if(this.age==o.age) 0
    else -1
  }
}

class TypeVariableBound {
  def compare[T <: Comparable[T]](first:T,second:T)={
    if (first.compareTo(second)>0)
      first
    else
      second
  }
}

object TypeVariableBound{
  def main(args: Array[String]): Unit = {
      val tvb=new TypeVariableBound
      println(tvb.compare("A", "B"))
      //此时下面这条语句是合法的,因为
      //Person类实现了Comparable接口
      println(tvb.compare(Person("stephen",19), Person("john",20)))
  }
}

上面的类型变量界定都是作用于方法Compare上,类型变量界定除了作用于方法上外,还可以对类中的泛型进行范围限定,例如:

//定义Student类为case class,且泛型T的类型变量界定为AnyVal
//在创建类时,所有处于AnyVal类继承层次结构的类都是合法的
//如Int、Double等值类型
case class Student[S,T <: AnyVal](var name:S,var hight:T)
object TypeVariableBound{
  def main(args: Array[String]): Unit = {
     //下面这条语句是不合法的,因为String类型不属于
     //AnyVal类层次结构
     // val S1=Student("john","170")
      //下面这两条语句都是合法的,因为
      //Int,Long类型都是AnyVal
      val S2=Student("john",170.0)
      val S3=Student("john",170L)

  }
}

从上面的例子中不难看出,类型变量界定可以对方法和类中的泛型进行范围界定,这种界定建立在类继承层次结构的基础之上,通过<:符号将泛型的范围进行一步减少。

2. 视图界定(View Bound)

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

//使用的是类型变量界定
case class Student[T,S <: Comparable[S]](var name:T,var height:S)
object ViewBound extends App{

  val s= Student("john","170")
  //下面这条语句不合法,这是因为
  //Int类型没有实现Comparable接口
  val s2= Student("john",170)
}

上面这个问题可以通过视图界定来解决,代码如下:

//利用<%符号对泛型S进行限定
//它的意思是S可以是Comparable类继承层次结构
//中实现了Comparable接口的类
//也可以是能够经过隐式转换得到的类,该类实现了
//Comparable接口
case class Student[T,S <% Comparable[S]](var name:T,var height:S)

object ViewBound extends App{
  val s= Student("john","170")
  //下面这条语句在视图界定中是合法的
  //因为Int类型此时会隐工转换为
  //RichInt类,而RichInt类属于Comparable、
  //继承层次结构
  val s2= Student("john",170)
}

查看Scala API文档可以看到

Int类会隐式转换成RichInt类,RichInt并不是直接实现Comparable口,而是通过ScalaNumberProxy类将Comparable中的方法继承过来:

ScalaNumberProxy混入了OrderedProxy,而OrderedProxy又混入了Ordered

trait Ordered混入了Comparable接口

可以看到,视图界定比类型变量界定的限制要宽松一点,它不但可以是类继承层次结构中的类,也可以跨越类继承层次结构,这后台的实现方式是通过隐式转换来进行的。

3. 上界(Upper Bound)与下界(Lower Bound)

下类代码其实是类型参数中经常提到的上界,这是因为它限定了继承层次结构中最顶层的类,例如T <: AnyVal表示泛型T的类型的最顶层类是AnyVal,所有输入是AnyVal的子类都是合法的,其它的都是非法的,因为被称为上界,有点像x<=3这样的数学比较。

case class Student[S,T <: AnyVal](var name:S,var hight:T)

除了上界之外,还有个非常重要的内容就是下界,下界通过>:符号来标识,代码如下:

class Pair1[T](val first:T,val second:T){
  //下界通过[R >: T]的意思是
  //泛型R的类型必须是T的超类
  def replaceFirst[R >: T](newFirst:R)= new Pair1[R](newFirst,second)
  override def toString()=first+"---"+second
}

//Book类
class Book(val name:String){
  override def toString()="name--"+name
}
//Book子类Ebook
class Ebook(name:String) extends Book(name)
//Book子类Pbook
class Pbook(name:String) extends Book(name)
//Pbook子类,WeridBook
class WeirdBook(name:String) extends Pbook(name)

object LowerBound extends App{

   val first = new Ebook("hello")
   val second = new Pbook("paper book")

   val p1 = new Pair1(first,second)
   println(p1)
    //scala> val p1 = new Pair1(first,second)
   //p1: Pair1[Book] = name--hello---name--paper book
   //Ebook,Pbook,最终得到的类是Pair1[Book]

   val newFirst = new Book("generic pBook")
   val p2 = p1.replaceFirst(newFirst)
   //p2: Pair1[Book] = name--generic pBook---name--paper book
   println(p2)

   val weirdFirst:WeirdBook= new WeirdBook("generic pBook")
   val p3 = p1.replaceFirst(weirdFirst)
   //p3: Pair1[Book] = name--generic pBook---name--paper book

   val p4 = new Pair1(second,second)
   //p4: Pair1[Pbook] = name--paper book---name--paper book
   println(p4)
   val thirdBook=new Book("Super Books")
   val p5=p4.replaceFirst(thirdBook)
   println(p5)

   //下面这条语句会报错
   //type mismatch; found : cn.scala.xtwy.lowerbound.Pair1[cn.scala.xtwy.lowerbound.Pbook] required: cn.scala.xtwy.lowerbound.Pbook
   val p6:Pbook=p4.replaceFirst(weirdFirst)
}

通过上述代码发现,如果newFirst的类型刚好是T的基类,R就直接是newFirst的类型。如果newFirst的类型不是T的基类,那R就会是T和newFirst的类型的共同基类。当限定返回变量类型时,例如val p6:Pbook=p4.replaceFirst(weirdFirst),由于p4为Pair1[Pbook],也即T为Pbook类型,而replaceFirst(weirdFirst)中的weirdFirst为Pbook的子类,违反了R>:T的下界限定,从而编译出错。从这里我们可以看到,下界的作用主要是保证类型安全

添加公众微信号,可以了解更多最新Spark、Scala相关技术资讯

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-29 19:11:08

Scala入门到精通——第十七节 类型参数(一)的相关文章

Scala入门到精通——第二十节 类型参数(二)

本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对scala中的Ordering与Ordered之间的关联与区别进行讲解,先看Ordering.Ordered的类继承层次体系: 通过上面两个图可以看到,Ordering混入了java中的Comparator接口,而Ordered混入了java的Comparable接口,我们知道java中的Comparator是一

Scala入门到精通——第二十一节 类型参数(三)-协变与逆变

作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 协变 逆变 类型通匹符 1. 协变 协变定义形式如:trait List[+T] {} .当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S]可以泛化为List[A].也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变(covariance). 图1 协变示意图 为方便大家理解,我们先分析java语言中为什么不存在协变及下一

Scala入门到精通——第二十七节 Scala操纵XML

本节主要内容 XML 字面量 XML内容提取 XML对象序列化及反序列化 XML文件读取与保存 XML模式匹配 1. XML 字面量 XML是一种非常重要的半结构化数据表示方式,目前大量的应用依赖于XML,这些应用或利用XML作为数据交换格式,或利用XML进行文件配置等.像JAVA.C++及其它流行的程序开发语言都是依赖于第三方库来实现XML的操作,例如JAVA经常通过JDOM,DOM4J等XML处理工具进行XML的操纵,但Scala提供了对XML的原生支持,通过scala.xml._包下的类或

Scala入门到精通——第二十节 类型參数(二)

本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对scala中的Ordering与Ordered之间的关联与差别进行解说,先看Ordering.Ordered的类继承层次体系: 通过上面两个图能够看到,Ordering混入了java中的Comparator接口.而Ordered混入了java的Comparable接口.我们知道java中的Comparator是一

Scala入门到精通——第十节 Scala类层次结构、Traits初步

本节主要内容 Scala类层次结构总览 Scala中原生类型的实现方式解析 Nothing.Null类型解析 Traits简介 Traits几种不同使用方式 1 Scala类层次结构 Scala中的类层次结构图如下: 来源:Programming in Scala 从上面的类层次结构图中可以看到,处于继承层次最顶层的是Any类,它是scala继承的根类,scala中所有的类都是它的子类 Any类中定义了下面几个方法: //==与!=被声明为final,它们不能被子类重写 final def ==

Scala入门到精通——第十一节 Trait进阶

本节主要内容 trait构造顺序 trait与类的比较 提前定义与懒加载 trait扩展类 self type 1 trait构造顺序 在前一讲当中我们提到,对于不存在具体实现及字段的trait,它最终生成的字节码文件反编译后是等同于java中的接口,而对于存在具体实现及字段的trait,其字节码文件反编译后得到的java中的抽象类,它有着scala语言自己的实现方式.因此,对于trait它也有自己的构造器,trait的构造器由字段的初始化和其它trait体中的语句构成,下面是其代码演示: pa

Scala入门到精通——第三十节 Scala脚本编程与结束语

本节主要内容 REPL命令行高级使用 使用Scala进行Linux脚本编程 结束语 1. REPL命令行高级使用 在使用REPL命令行时,有时候我们需要粘贴的代码比较大,而普通的粘贴可能会些一些问题,比如中文粘贴会出现乱码.多行代码粘贴时会出错,此时需要用到REPL的高级功能.在日常开发过程中,我们粘贴多行代码的时候会遇到下列问题: //本意是要粘贴下面两行代码 class Person(val name:String,val age:Int) val p=new Person("摇摆少年梦&q

Scala入门到精通——第二十九节 Scala数据库编程

本节主要内容 Scala Mavenproject的创建 Scala JDBC方式訪问MySQL Slick简单介绍 Slick数据库编程实战 SQL与Slick相互转换 本课程在多数内容是在官方教程上改动而来的,官方给的样例是H2数据库上的.经过本人改造,用在MySQL数据库上,官方教程地址:http://slick.typesafe.com/doc/2.1.0/sql-to-slick.html 1. Scala Mavenproject的创建 本节的project项目採用的是Maven P

Scala入门到精通——第十二节 I/O与正则表达式

本节主要内容 Scala I/O操作简介 Scala 写文件 Scala 读文件 Scala 网络I/O 正则表达式简介 Scala正则表达式实战 1. Scala I/O操作简介 I/O操作是一门编程语言中的重要内容,在Scala中,它更多的是调用java中的I/O类或者通过对java中的I/O类进行相应的封装来实现I/O操作.在上一节内容中我们已经用到了I/O操作: trait FileLogger extends Logger{ val fileName:String //PrintWri