Scala
提供了另外一種類型界定的手段,與類型界定操做符<:, <%
具備微妙的關係。api
T <=< U
:T
是否與U
類型相等app
T <:< U
:T
是不是U
的子類型ide
T <%< U
:T
是否能夠隱式地轉換爲U
類型函數
類型界定經常用於如下兩種場景:性能
在泛型類中,定義在特定條件下的才能使用的方法ui
協助類型推演this
以T <:< U
爲例,capital
的類型爲List[(String, String)]
,經過調用toMap
方法可將其轉換爲Map[String,String]
的類型,這樣的轉換僅當List
的元素類型爲Tuple2
才合法。scala
val capital = List( "US" -> "Washington", "France" -> "Paris" "Japan" -> "Tokyo") capital.toMap // OK
當List
的元素類型不是Tuple2
時,試圖調用toMap
是編譯失敗的。code
val phones = List("+9519728872", "+9599820012") phones.toMap // error: Cannot prove that String <:< (T, U).
其中,toMap
定義在TraversableOnce
特質中。orm
trait TraversableOnce[+A] { def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] = { val b = immutable.Map.newBuilder[T, U] for (x <- self) b += x b.result } }
其中「隱私參數」implicit ev: A <:< (T, U)
的表示中,<::<
其本質是一個泛型類,其定義在scala.Predef
中。更有甚者,A <:< (T, U)
實際上是<:<[A, (T, U)]
的中綴表示。其中,<::<
是一個具備兩個類型參數的泛型類。
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.") sealed abstract class <:<[-From, +To] extends (From => To)
From => To
實際上是Function1[From, To]
特質的一個語法糖。也就是說,<:<
其本質是一個一元函數。接下來的一個疑問是:對於implicit ev: A <:< (T, U)
,編譯器如何找到對應的「隱式值」呢?
事實上「隱式值」默認由Predef.conforms
的工廠方法提供,它是一個無參的隱式轉換函數。
implicit def conforms[A]: A <:< A = new <:<[A,A] { override def apply(a: A): A = a }
val capital = List( "US" -> "Washington", "France" -> "Paris" "Japan" -> "Tokyo") capital.toMap() // OK
對於此例,conforms[(String, String)]
生成<:<[(String, String), (String, String)]
類型的隱式值。
標準庫爲了改善性能,避免每次調用toMap
時都new
一個<:<[A,A]
類型的實例,引入了享元模式。
private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x } implicit def conforms[A]: A <:< A = { singleton_<:<.asInstanceOf[A <:< A] }
<:
與<:<
def foo[A](i:A)(implicit ev : A <:< Serializable) = i foo(1) // error foo("hi") // ok def bar[A <: Serializable](i:A) = i bar(1) // compile error bar("hi") // ok
<:
與<:<
之間到底有什麼區別呢?<:
是一個類型限定操做符,編譯器保證其子類型的約束關係;而<:<
是一個類型,編譯器證實其子類型的約束關係。二者在使用場景,類型推演等方面存在微妙的差別。
def foo[A, B <: A](a: A, b: B) = (a,b) foo(1, List(1,2,3)) // (Any, List[Int]) = (1,List(1, 2, 3))
傳入第一個參數是Int
類型,第二個參數是List[Int]
,顯然這不符合B <: A
的約束。爲了知足這個約束,編譯器會繼續向上尋找最近的公共父類型來匹配,因而在第一個參數進一步推演爲Any
,使得List[Int]
剛好符合Any
的子類型。
def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a, b) bar(1, List(1,2,3)) // error: Cannot prove that List[Int] <:< Int.
而<:<
則更加嚴格,對於本例由於類型推演優先於隱式解析,第一個參數是Int
類型,第二個參數是List[Int]
,編譯器試圖證實List[Int] <:< Int
而當即失敗。