Scala入門到精通——第二十節 型別引數(二)
本節主要內容
- Ordering與Ordered特質
- 上下文界定(Context Bound)
- 多重界定
- 型別約束
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相關技術資訊