1. 程式人生 > >Scala入門到精通——第二十節 類型參數(二)

Scala入門到精通——第二十節 類型參數(二)

ger 直觀 implicit 有時 com 方法調用 錯誤 println there

本節主要內容

  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相關技術資訊
技術分享

Scala入門到精通——第二十節 類型參數(二)