Scala类型参数(二)

冷不防 2022-06-07 02:15 226阅读 0赞

Scala类型参数(二)

1 Ordering与Ordered特质

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

scala_typevariable_2_1.png

scala_typevariable_2_2.png

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

  1. //下面是定义的Person类(Java)
  2. public class Person {
  3. private String name;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. Person(String name){
  11. this.name=name;
  12. }
  13. }
  14. //Comparator接口,注意它是在java.util包中的
  15. public class PersonCompartor implements Comparator<Person>{
  16. @Override
  17. public int compare(Person o1, Person o2) {
  18. if (o1.getName().equalsIgnoreCase(o2.getName())) {
  19. return 1;
  20. }else{
  21. return -1;
  22. }
  23. }
  24. public static void main(String[] args){
  25. PersonCompartor pc=new PersonCompartor();
  26. Person p1=new Person("摇摆少年梦");
  27. Person p2=new Person("摇摆少年梦2");
  28. //下面是它的对象比较使用方式
  29. //可以看它,这是通过外部对象进行方法调用的
  30. if(pc.compare(p1, p2)>0) {
  31. System.out.println(p1);
  32. }else{
  33. System.out.println(p2);
  34. }
  35. }
  36. }

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

  1. public class Person implements Comparable<Person>{
  2. private String name;
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. Person(String name){
  10. this.name=name;
  11. }
  12. @Override
  13. public int compareTo(Person o) {
  14. if (this.getName().equalsIgnoreCase(o.getName())) {
  15. return 1;
  16. }else{
  17. return -1;
  18. }
  19. }
  20. }
  21. public class PersonComparable {
  22. public static void main(String[] args) {
  23. Person p1=new Person("摇摆少年梦");
  24. Person p2=new Person("摇摆少年梦2");
  25. //对象自身与其它对象比较,而不需要借助第三方
  26. if(p1.compareTo(p2)>0) {
  27. System.out.println(p1);
  28. }else{
  29. System.out.println(p2);
  30. }
  31. }
  32. }

从上述代码中可以看到Comparable与Comparator接口两者的本质不同,因此Ordering混入了Comparator,Ordered混入了Comparable,它们之间的区别和Comparable与Comparator间的区别是相同的。这里先给出一个Ordered在scala中的用法,Ordering的用法,将在上下文界定的时候具体讲解

  1. case class Student(val name:String) extends Ordered[Student]{
  2. override def compare(that:Student):Int={
  3. if(this.name==that.name)
  4. 1
  5. else
  6. -1
  7. }
  8. }
  9. //将类型参数定义为T<:Ordered[T]
  10. class Pair1[T<:Ordered[T]](val first:T,val second:T){
  11. //比较的时候直接使用<符号进行对象间的比较
  12. def smaller()={
  13. if(first < second)
  14. first
  15. else
  16. second
  17. }
  18. }
  19. object OrderedViewBound extends App{
  20. val p=new Pair1(Student("摇摆少年梦"),Student("摇摆少年梦2"))
  21. println(p.smaller)
  22. }

2 上下文界定

上下文界定采用隐式值来实现,上下文界定的类型参数形式为T:M的形式,其中M是一个泛型,这种形式要求存在一个M[T]类型的隐式值:

  1. //PersonOrdering混入了Ordering,它与实现了Comparator接口的类的功能一致
  2. class PersonOrdering extends Ordering[Person]{
  3. override def compare(x:Person, y:Person):Int={
  4. if(x.name>y.name)
  5. 1
  6. else
  7. -1
  8. }
  9. }
  10. case class Person(val name:String){
  11. println("正在构造对象:" + name)
  12. }
  13. // 下面的代码定义了一个上下文界定
  14. // 它的意思是在对应作用域中,必须存在一个类型为Ordering[T]的隐式值,该隐式值可以作用于内部的方法
  15. class Pair[T:Ordering](val first:T,second:T) {
  16. // smaller方法中有一个隐式参数,该隐式参数类型为Ordering[T]
  17. def smaller(implicit ord:Ordering[T]) = {
  18. if(ord.compare(first,second) > 0)
  19. first
  20. else
  21. second
  22. }
  23. }
  24. object ConextBound extends App{
  25. //定义一个隐式值,它的类型为Ordering[Person]
  26. implicit val p1=new PersonOrdering
  27. val p=new Pair(Person("123"),Person("456"))
  28. //不给函数指定参数,此时会查找一个隐式值,该隐式值类型为Ordering[Person],根据上下文界定的要求,该类型正好满足要求
  29. //因此它会作为smaller的隐式参数传入,从而调用ord.compare(first, second)方法进行比较
  30. println(p.smaller)
  31. }

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

  1. class PersonOrdering extends Ordering[Person]{
  2. override def compare(x:Person, y:Person):Int={
  3. if(x.name>y.name)
  4. 1
  5. else
  6. -1
  7. }
  8. }
  9. case class Person(val name:String){
  10. println("正在构造对象:"+name)
  11. }
  12. class Pair[T:Ordering](val first:T,val second:T){
  13. //引入odering到Ordered的隐式转换
  14. //在查找作用域范围内的Ordering[T]的隐式值
  15. //本例的话是implicit val p1=new PersonOrdering
  16. //编译器看到比较方式是<的方式进行的时候,会自动进行
  17. //隐式转换,转换成Ordered,然后调用其中的<方法进行比较
  18. import Ordered.orderingToOrdered;
  19. def smaller={
  20. if(first<second)
  21. first
  22. else
  23. second
  24. }
  25. }
  26. object ConextBound extends App{
  27. implicit val p1=new PersonOrdering
  28. val p=new Pair(Person("123"),Person("456"))
  29. println(p.smaller)
  30. }

3 多重界定

多重界定具有多种形式,例如:
- T:M:K //这意味着在作用域中必须存在M[T]、K[T]类型的隐式值
- T<%M<%K //这意味着在作用域中必须存在T到M、T到K的隐式转换
- K>:T<:M //这意味着M是T类型的超类,K也是T类型的超类

  1. class A[T]
  2. class B[T]
  3. object MutilBound extends App{
  4. implicit val a=new A[String]
  5. implicit val b=new B[String]
  6. //多重上下文界定,必须存在两个隐式值,类型为A[T],B[T]类型
  7. //前面定义的两个隐式值a,b便是
  8. def test[T:A:B](x:T)=println(x)
  9. test("摇摆少年梦")
  10. implicit def t2A[T](x:T)=new A[T]
  11. implicit def t2B[T](x:T)=new B[T]
  12. //多重视图界定,必须存在T到A,T到B的隐式转换
  13. //前面我们定义的两个隐式转换函数就是
  14. def test2[T <% A[T] <% B[T]](x:T)=println(x)
  15. test2("摇摆少年梦2")
  16. }

4 类型约束

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

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

  1. T=:=U //用于判断T是否等于U
  2. T<:<U //用于判断T是否为U的子类

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

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

用法简介:

  1. object TypeConstraint extends App{
  2. def test[T](name:T)(implicit ev: T <:< java.io.Serializable)= { name }
  3. //正确,因为String类型属于Serializable的子类
  4. println(test("摇摆少年梦"))
  5. //错误,因为Int类型不属于Seriablizable的子类
  6. println(test(134))
  7. }

那么问题来了,test方法定义了一个隐式参数,它的类型是T <:< java.io.Serializable,即只有T为java.io.Serializable的子类才满足要求,但是我们在程序中并没有指定隐式值,为什么这样也是合法的呢?这是因为Predef中的conforms方法会为我们产生一个隐式值。
那类型约束<:<与类型变量界定<:有什么区别呢?下面给出的代码似乎告诉我们它们之间好像也没有什么区别:

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

但下面的代码给我们演示的是类型约束<:<与类型变量界定<:之间的区别:
下面的代码演示的是其在一般函数使用时的区别

  1. scala> def foo[A, B <: A](a: A, b: B) = (a,b)
  2. foo: [A, B <: A](a: A, b: B)(A, B)
  3. //类型不匹配时,采用类型推断
  4. scala> foo(1, List(1,2,3))
  5. res0: (Any, List[Int]) = (1,List(1, 2, 3))
  6. //严格匹配,不会采用类型推断
  7. scala> def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a,b)
  8. bar: [A, B](a: A, b: B)(implicit ev: <:<[B,A])(A, B)
  9. scala> bar(1,List(1,2,3))
  10. <console>:9: error: Cannot prove that List[Int] <:< Int.
  11. bar(1,List(1,2,3))
  12. ^

下面的代码给出的是其在隐式转换时的区别

  1. scala> def foo[B, A<:B] (a:A,b:B) = print("OK")
  2. foo: [B, A <: B](a: A, b: B)Unit
  3. scala> class A; class B;
  4. defined class A
  5. defined class B
  6. scala> implicit def a2b(a:A) = new B
  7. warning: there were 1 feature warning(s); re-run with -feature for details
  8. a2b: (a: A)B
  9. //经过隐式转换后,满足要求
  10. scala> foo(new A, new B)
  11. OK
  12. scala> def bar[A,B](a:A,b:B)(implicit ev: A<:<B) = print("OK")
  13. bar: [A, B](a: A, b: B)(implicit ev: <:<[A,B])Unit
  14. //可以看到,隐式转换在<:<类型约束中不管用
  15. scala> bar(new A, new B)
  16. <console>:12: error: Cannot prove that A <:< B.
  17. bar(new A, new B)
  18. ^

参考博文

https://yq.aliyun.com/articles/60374?spm=5176.8251999.569296.20.22998d8bCAXue4

发表评论

表情:
评论列表 (有 0 条评论,226人围观)

还没有评论,来说两句吧...

相关阅读

    相关 13.Scala-类型参数

    第13章 类型参数 13.1 泛型类 类和特质都可以带类型参数,用方括号来定义类型参数,可以用类型参 数来定义变量、方法参数和返回值。带有一个或多个类型参数的...

    相关 scala array类型参数

    在Scala中,数组(Array)是一种用于存储相同类型元素的数据结构。数组可以用于保存基本数据类型和自定义数据类型的元素。当定义数组类型参数时,您通常是在函数、类或方法签名中

    相关 Scala类型参数(一)

    Scala类型参数(一) 类型参数是对泛型的范围进一步的界定,那么介绍类型参数之前先聊聊泛型。Scala类型参数。类型参数是对泛型的范围进一步的界定,那么介绍类型参数之前

    相关 Scala类型参数

      类型参数是什么?  类型参数其实就类似于Java中的泛型。也是定义一种类型参数,比如在集合,在类,在函数中, 定义类型参数,然后就可以保证使用到该类型参数的地方,就

    相关 Scala中的类型参数

    类型参数是什么?类型参数其实就类似于Java中的泛型。先说说Java中的泛型是什么,比如我们有List a = new ArrayList(),接着a.add(1),没问题,a