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

本节主要内容

  1. Ordering与Ordered特质
  2. 上下文界定(Context Bound)
  3. 多重界定
  4. 类型约束

1. Ordering与Ordered特质

在介绍上下文界定之前,我们对scala中的Ordering与Ordered之间的关联与差别进行解说,先看Ordering、Ordered的类继承层次体系:

通过上面两个图能够看到,Ordering混入了java中的Comparator接口。而Ordered混入了java的Comparable接口。我们知道java中的Comparator是一个外部比較器。而Comparable则是一个内部比較器,比如:

//以下是定义的Person类(Java)
public class Person {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    Person(String name){
        this.name=name;
    }
}
//Comparator接口,注意它是在java.util包中的
public class PersonCompartor implements Comparator<Person>{

    @Override
    public int compare(Person o1, Person o2) {
        if (o1.getName().equalsIgnoreCase(o2.getName())) {
            return 1;
        }else{
            return -1;
        }
    }

    public static void main(String[] args){
        PersonCompartor pc=new PersonCompartor();
        Person p1=new Person("摇摆少年梦");
        Person p2=new Person("摇摆少年梦2");
        //以下是它的对象比較使用方式
        //能够看它。这是通过外部对象进行方法调用的
        if(pc.compare(p1, p2)>0) {
            System.out.println(p1);
        }else{
            System.out.println(p2);
        }
    }

}

而Comparable接口是用于内部比較,Person类自己实现Comparable接口。代码例如以下

public class Person implements Comparable<Person>{
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    Person(String name){
        this.name=name;
    }
    @Override
    public int compareTo(Person o) {
        if (this.getName().equalsIgnoreCase(o.getName())) {
            return 1;
        }else{
            return -1;
        }
    }
}

public class PersonComparable {

    public static void main(String[] args) {
        Person p1=new Person("摇摆少年梦");
        Person p2=new Person("摇摆少年梦2");
        //对象自身与其他对象比較,而不须要借助第三方
        if(p1.compareTo(p2)>0) {
            System.out.println(p1);
        }else{
            System.out.println(p2);
        }

    }

}

从上述代码中能够看到Comparable与Comparator接口两者的本质不同。因此Ordering混入了Comparator,Ordered混入了Comparator,它们之间的差别和Comparable与Comparator间的差别是同样的。

这里先给出一个Ordered在scala中的使用方法,Ordering的使用方法,将在上下文界定的时候详细解说

case class Student(val name:String) extends Ordered[Student]{
  override def compare(that:Student):Int={
    if(this.name==that.name)
      1
    else
      -1
  }
}

//将类型參数定义为T<:Ordered[T]
class Pair1[T<:Ordered[T]](val first:T,val second:T){
  //比較的时候直接使用<符号进行对象间的比較
  def smaller()={
    if(first < second)
      first
    else
      second
  }
}

object OrderedViewBound extends App{
  val p=new Pair1(Student("摇摆少年梦"),Student("摇摆少年梦2"))
  println(p.smaller)
}

2. 上下文界定

在第十七节中的类型參数(一)中,我们提到视图界定能够跨越类继承层次结构,其后面的原理是隐式转换。本节要介绍的上下文界定採用隐式值来实现。上下文界定的类型參数形式为T:M的形式。当中M是一个泛型,这样的形式要求存在一个M[T]类型的隐式值:

//PersonOrdering混入了Ordering,它与实现了Comparator接口的类的功能一致
class PersonOrdering extends Ordering[Person]{
   override def compare(x:Person, y:Person):Int={
    if(x.name>y.name)
      1
    else
      -1
  }
}
case class Person(val name:String){
  println("正在构造对象:"+name)
}
//以下的代码定义了一个上下文界定
//它的意思是在相应作用域中,必须存在一个类型为Ordering[T]的隐式值,该隐式值能够作用于内部的方法
class Pair[T:Ordering](val first:T,val second:T){
  //smaller方法中有一个隐式參数。该隐式參数类型为Ordering[T]
  def smaller(implicit ord:Ordering[T])={
    if(ord.compare(first, second)>0)
      first
    else
      second
  }
}

object ConextBound extends App{
  //定义一个隐式值。它的类型为Ordering[Person]
  implicit val p1=new PersonOrdering
  val p=new Pair(Person("123"),Person("456"))
  //不给函数指定參数,此时会查找一个隐式值,该隐式值类型为Ordering[Person],依据上下文界定的要求。该类型正好满足要求
  //因此它会作为smaller的隐式參数传入,从而调用ord.compare(first, second)方法进行比較
  println(p.smaller)
}

有时候也希望ord.compare(first, second)>0的比較形式能够写为first > second这样的直观比較形式,此时能够省去smaller函数的隐式參数。并引入Ordering到Ordered的隐式转换,代码例如以下:

class PersonOrdering extends Ordering[Person]{
   override def compare(x:Person, y:Person):Int={
    if(x.name>y.name)
      1
    else
      -1
  }
}
case class Person(val name:String){
  println("正在构造对象:"+name)
}

class Pair[T:Ordering](val first:T,val second:T){
  //引入odering到Ordered的隐式转换
  //在查找作用域范围内的Ordering[T]的隐式值
  //本例的话是implicit val p1=new PersonOrdering
  //编译器看到比較方式是<的方式进行的时候。会自己主动进行
  //隐式转换,转换成Ordered。然后调用当中的<方法进行比較
  import Ordered.orderingToOrdered;
  def smaller={
    if(first<second)
      first
    else
      second
  }
}

object ConextBound extends App{
  implicit val p1=new PersonOrdering
  val p=new Pair(Person("123"),Person("456"))
  println(p.smaller)
}

3. 多重界定

多重界定具有多种形式,比如:

T:M:K //这意味着在作用域中必须存在M[T]、K[T]类型的隐式值

T<%M<%K //这意味着在作用域中必须存在T到M、T到K的隐式转换

K>:T<:M //这意味着M是T类型的超类,K也是T类型的超类

…..

class A[T]
class B[T]

object MutilBound extends App{
  implicit val a=new A[String]
  implicit val b=new B[String]
  //多重上下文界定。必须存在两个隐式值,类型为A[T],B[T]类型
  //前面定义的两个隐式值a,b便是
  def test[T:A:B](x:T)=println(x)
  test("摇摆少年梦")

  implicit def t2A[T](x:T)=new A[T]
  implicit def t2B[T](x:T)=new B[T]
  //多重视图界定。必须存在T到A。T到B的隐式转换
  //前面我们定义的两个隐式转换函数就是
  def test2[T <% A[T] <% B[T]](x:T)=println(x)
  test2("摇摆少年梦2")
}

4. 类型约束

本节部分实验来自:http://hongjiang.info/scala-type-contraints-and-specialized-methods/,感谢原作者的无私奉献

前面讲的类型变量界定、视图界定都是将泛型限定在一定范围内,而上下文界定则是将类型限定为某一类型。类型约束与下下文界定类型,仅仅只是它是用于推断类型測试,类型约束有以下两种:

T=:=U  //用于推断T是否等于U
T<:<U  //用于推断T是否为U的子类

像上面的=:=符号非常像一个操作符,但事实上它是scala语言中的类,它们被定义在Predef当中

@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.")
  sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
  // not in the <:< companion object because it is also
  // intended to subsume identity (which is no longer implicit)
  implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]  

 @implicitNotFound(msg = "Cannot prove that ${From} =:= ${To}.")
  sealed abstract class =:=[From, To] extends (From => To) with Serializable
  private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x }
  object =:= {
     implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
  }

使用方法简单介绍:

object TypeConstraint extends App{
   def test[T](name:T)(implicit ev: T <:< java.io.Serializable)= { name }
   //正确。由于String类型属于Serializable的子类
   println(test("摇摆少年梦"))
   //错误。由于Int类型不属于Seriablizable的子类
   println(test(134))
}

那么问题来了,test方法定义了一个隐式參数,它的类型是T <:< java.io.Serializable。即仅仅有T为java.io.Serializable的子类才满足要求,可是我们在程序中并没有指定隐式值。为什么这样也是合法的呢?这是由于Predef中的conforms方法会为我们产生一个隐式值。

那类型约束<:<与类型变量界定<:有什么差别呢?以下给出的代码似乎告诉我们它们之间好像也没有什么差别:

   def test1[T<:java.io.Serializable](name:T)= { name }
   //编译通过,符合类型变量界定的条件
   println(test1("摇摆少年梦"))
   //编译通只是,不符号类型变量界定的条件
   println(test1(134))

但以下的代码给我们演示的是类型约束<:<与类型变量界定<:之间的差别:

以下的代码演示的是其在一般函数使用时的差别

 scala> def foo[A, B <: A](a: A, b: B) = (a,b)
foo: [A, B <: A](a: A, b: B)(A, B)

//类型不匹配时。採用类型推断
scala>     foo(1, List(1,2,3))
res0: (Any, List[Int]) = (1,List(1, 2, 3))

//严格匹配。不会採用类型推断
scala>  def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a,b)
bar: [A, B](a: A, b: B)(implicit ev: <:<[B,A])(A, B)

scala>     bar(1,List(1,2,3))
<console>:9: error: Cannot prove that List[Int] <:< Int.
                  bar(1,List(1,2,3))
                     ^

以下的代码给出的是其在隐式转换时的差别

scala> def foo[B, A<:B] (a:A,b:B) = print("OK")
foo: [B, A <: B](a: A, b: B)Unit

scala> class A; class B;
defined class A
defined class B

scala> implicit def a2b(a:A) = new B
warning: there were 1 feature warning(s); re-run with -feature for details
a2b: (a: A)B
//经过隐式转换后,满足要求
scala>  foo(new A, new B)
OK
scala>  def bar[A,B](a:A,b:B)(implicit ev: A<:<B) = print("OK")
bar: [A, B](a: A, b: B)(implicit ev: <:<[A,B])Unit
//能够看到,隐式转换在<:<类型约束中无论用
scala> bar(new A, new B)
<console>:12: error: Cannot prove that A <:< B.
              bar(new A, new B)
                 ^

加入公众微信号,能够了解很多其他最新Spark、Scala相关技术资讯

时间: 2025-01-18 11:18:05

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

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

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

Scala入门到精通——第二十四节 高级类型 (三)

作者:摆摆少年梦 视频地址:http://blog.csdn.net/wsscy2004/article/details/38440247 本节主要内容 Type Specialization Manifest.TypeTag.ClassTag Scala类型系统总结 在scala中,类(class)与类型(type)是两个不一样的概念.我们知道类是对同一类型数据的抽象,而类型则更详细. 比方定义class List[T] {}, 能够有List[Int] 和 List[String]等详细类型

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入门到精通——第二十二节 高级类型 (一)

作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 this.type使用 类型投影 结构类型 复合类型 1. this.type使用 class Person{ private var name:String=null private var age:Int=0 def setName(name:String)={ this.name=name //返回对象本身 this } def setAge(age:Int)={ this.a

Scala入门到精通——第二十五节 提取器(Extractor)

作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 apply与unapply方法 零变量或变量的模式匹配 提取器与序列模式 scala中的占位符使用总结 1. apply与unapply方法 apply方法我们已经非常熟悉了,它帮助我们无需new操作就可以创建对象,而unapply方法则用于析构出对象,在模式匹配中特别提到,如果一个类要能够应用于模式匹配当中,必须将类声明为case class,因为一旦被定义为case class,

Scala入门到精通——第二十六节 Scala并发编程基础

作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 Scala并发编程简介 Scala Actor并发编程模型 react模型 Actor的几种状态 Actor深入使用解析 本节主要介绍的scala并发编程的基本思想,由于scala在2.10版本之后宣布使用akka作为其并发编程库,因此本节只进行基础性的内容介绍,后面将把重点放在akka框架的讲解上. 1. Scala并发编程简介 2003 年,Herb Sutter 在他的文章 "

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

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

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入门到精通——第二十八节 Scala与JAVA互操作

本节主要内容 JAVA中调用Scala类 Scala中调用JAVA类 Scala类型参数与JAVA泛型互操作 Scala与Java间的异常处理互操作 1. JAVA中调用Scala类 Java可以直接操作纵Scala类,如同Scala直接使用Java中的类一样,例如: //在Person.scala文件中定义Scala语法的Person类 package cn.scala.xtwy.scalaToJava class Person(val name:String,val age:Int) //伴