scala 中隱式相較於java 來講是一種全新的特性。那麼隱式到底是什麼呢?
隱式存在三種基本使用方式:
- 隱式屬性
- 隱式方法
- 隱式對象java
隱式屬性例如python
implicit val int size = 5
def caculate(a : Int)(implicit val size) = a * size
在這種狀況下,隱式是做爲一種上下文屬性進行輸入,更復雜的狀況就像上文中講到的執行上下文和功能控制。其本質上是一種資源的注入,隱藏/省略資源的顯示配置,而達到裝載的目的。
講到這裏相信很容易聯想到Spring中的IOC,在Spring/Guice的IOC中咱們經過註解/配置這種形式來實現注入資源實例化。Spring/Guice中對於注入的資源,咱們能夠經過xml或者註解完成資源屬性的配置,可是這種方法存在不足,xml或者註解配置都屬於靜態配置,若是咱們須要一些動態特性的時候須要額外的去作不少工做。
就好比咱們有一個支付Controller,在支付方式是美金的時候咱們須要調用美金服務,在支付方式爲人民幣的時候咱們須要調用人民幣服務。數據庫
class PayController @Inject()settlementService: SettlementService) { // 這種狀況下只能注入一種結算方式,沒法實現動態結算 def doSettle(bill: Bill) = { settlementService.doSettle(bill) } }
若是要實現這種結算,咱們就必須手動經過代碼在判斷 bill.getType 後手動實現對應結算功能。但這樣就引入了耦合。編程
而隱式屬性無疑是一種更好的方式,這裏能夠自由注入結算方式api
class PayController ()(implicit val settlementService: SettlementService) { def doSettle(bill: Bill) = { settlementService.doSettle(bill) } }
函數方法本質上是進行一種轉化,這種轉化不依賴上下文,也就是說
f(a) => b 不會影響任何其餘的狀態,也能夠稱做無反作用。
隱式函數方法,本質上也是一種函數方法,能夠看作是對於元素的一種轉化關係,由 a => b。
在java中 facade模式是比較經常使用的一種模式,facade模式提供的是對於接口信息的封裝。
在系統開發或業務開發中facade模式是使用比較頻繁的,在java中可能咱們對應不一樣系統接口會提供不一樣facade,但對於不一樣facade的轉化都須要在代碼中手動裝填。
經過方法級隱式轉化咱們能夠方便的實現接口級的隱式轉化。markdown
例以下文中咱們對於訂單進行擴展,實際結算的訂單可能涉及線上和線下兩種訂單,但最終訂單信息都會被轉化爲內部的 BillInfo。
這裏咱們經過方法級隱式轉化,直接實現 OnlineBillFacade/OfflineBillFacade => BillInfo
而不須要大量判斷代碼實現邏輯控制。session
sealed trait Bill
case class OnlineBillFacade(count: Int, platform: Platform, currency: Currency) extend Bill
case class OfflineBillFacade(count: Int, address: String, shop: Shop, currency: Currency) extend Bill
case class BillInfo( flowNumber: Long, createTime: Long, state: State, count: Int, platform: Platform, address: String, shop: Shop, currency: Currency) extend Bill
object BillConverter {
implicit def onlineBill2BillInfo(facade: OnlineBillFacade) : BillInfo = ...
implicit def offlineBillFacade(facade: OfflineBillFacade) : BillInfo = ...
}
class PayController ()(implicit val settlementService: SettlementService) {
def doSettle[T <: Bill](bill: T) = {
settlementService.doSettle((BillInfo)bill)
}
}
對於隱式對象,無疑是對於AOP思想的進一步的探索。在AOP中咱們想要不改變源碼還要增長功能,AOP中咱們經過動態代理實現功能的擴展。
經過動態代理,咱們能夠方便的實現切面控制。面向切面編程實際上有一個前提,就是咱們的一切其實都得圍繞接口進行設計,切面所能控制的最小粒度是就是方法級。
而且因爲是泛型配置,事實上若是要在切面中使用通知時,還須要對於輸入參數進行篩選判斷而完成泛型管理,這部分工做很不利於擴展,事實上這裏咱們沒有辦法對於泛型進行類型強約束。app
而隱式對象爲咱們帶來了全新的可能,因爲隱式對象是面向POJO,所以隱式對象相較AOP擁有更細的粒度控制。而且因爲是針對POJO,隱式對象不須要進行邊界界定。
經過隱式對象,咱們能夠真正在不改變原有代碼基礎上實現功能的擴展。函數
通用的上下文信息經過隱式默認實現
,下降耦合ui
編寫事務、數據庫鏈接、線程池以及用戶會話時隱式參數上下文也一樣適合使用。使用方法參數能組合行爲,而將方法參數設置爲隱式參數可以使 API 變得更加簡潔。
// 導入了可供編譯器使用的全局默認值
import scala.concurrent.ExecutionContext.Implicits.global
apply[T](body: => T)(implicit executor: ExecutionContext): Future[T]
經過引入受權令牌,咱們能夠控制某些特定的 API 操做只能供某些用戶調用,咱們也可使用受權令牌決定數據可見性,而隱式用戶會話參數也許就包含了這類令牌信息。
def createMenu(implicit session: Session): Menu = {
val defaultItems = List(helpItem, searchItem)
val accountItems =
if (session.loggedin()) List(viewAccountItem, editAccountItem)
else List(loginItem)
Menu(defaultItems ++ accountItems)
}
對具備參數化類型方法中的類型參數進行限定,使該參數只接受某些類型的輸入
package implicits
object Implicits {
import implicits.javadb.JRow
implicit class SRow (jrow: JRow){
def get[T](colName: String)(implicit toT: (JRow, String) => T): T =
toT(jrow, colName)
}
implicit val jrowToInt: (JRow, String) => Int = (jrow: JRow, colName: String) => jrow.getInt(colName)
implicit val jrowToDouble: (JRow, String) => Double = (jrow: JRow, colName: String) => jrow.getDouble(colName)
implicit val jrowToString: (JRow, String) => String = (jrow: JRow, colName: String) => jrow.getText(colName)
def main(args: Array[String]) = {
val row = javadb.JRow("one" -> 1, "two" -> 2.2, "three" -> "THREE!")
val oneValue1: Int = row.get("one")
val twoValue1: Double = row.get("two")
val threeValue1: String = row.get("three")
// val fourValue1: Byte = row.get("four")
// 不編譯該行
println(s"one1 -> $oneValue1")
println(s"two1 -> $twoValue1")
println(s"three1 -> $threeValue1")
val oneValue2 = row.get[Int]("one")
val twoValue2 = row.get[Double]("two")
val threeValue2 = row.get[String]("three")
// val fourValue2 = row.get[Byte]("four")
// 不編譯該行
println(s"one2 -> $oneValue2")
println(s"two2 -> $twoValue2")
println(s"three2 -> $threeValue2")
}
}
package database_api {
case class InvalidColumnName(name: String)
extends RuntimeException(s"Invalid column name $name")
trait Row {
def getInt (colName: String): Int
def getDouble (colName: String): Double
def getText (colName: String): String
}
}
package javadb {
import database_api._
case class JRow(representation: Map[String, Any]) extends Row {
private def get(colName: String): Any =
representation.getOrElse(colName, throw InvalidColumnName(colName))
def getInt (colName: String): Int = get(colName).asInstanceOf[Int]
def getDouble (colName: String): Double = get(colName).asInstanceOf[Double]
def getText (colName: String): String = get(colName).asInstanceOf[String]
}
object JRow {
def apply(pairs: (String, Any)*) = new JRow(Map(pairs :_*))
}
}
有時候,咱們只須要限定容許的類型,並不須要提供額外的處理。換句話說,咱們須要
「證據」證實提出的類型知足咱們的需求。如今咱們將討論另一種被稱爲隱式證據的相
關技術來對容許的類型進行限定,並且這些類型無需繼承某一共有的超類。
trait TraversableOnce[+A] ... {
...
def toMap[T, U](implicit ev: <:<[A, (T, U)]): immutable.Map[T, U]
...
}
咱們曾說起過,可使用中綴表示法表示由兩個類型參數所組成的類型,所以下列兩種表
達式是等價的:
<:<[A, B]
A <:< B
在 toMap 中, B 其實是一個 pair:
<:<[A, (T, U
object M {
implicit object IntMarker
implicit object StringMarker
def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit = println(s"Seq[Int]: $seq")
def m(seq: Seq[String])(implicit s: StringMarker.type): Unit =
println(s"Seq[String]: $seq")
}
相似於類型擦除,虛類型僅用於標記。
虛類型自己實際上不屬於隱式轉換的範疇,但這裏其實和類型擦除在使用之上有必定的類似之初。
虛類型主要有如下兩個優勢:
- 使無效狀態沒法表明。最好的體現就是 List、Cons 以及 Nil的關係
- 攜帶類型級別的一些信息
例如經過虛類型限制距離單位
case class Distance[A](x: Double) extends AnyVal
case object Kilometer
case object Mile
def marathonDistance: Distance[Kilometer.type] = Distance[Kilometer.type](42.195)
def distanceKmToMiles(kilos: Distance[Kilometer.type]): Distance[Mile.type] =
Distance[Mile.type](kilos.x * 0.621371)
def marathonDistanceInMiles: Distance[Mile.type] = distanceKmToMiles( marathonDistance )
@implicitNotFound(msg =
"Cannot construct a collection of type ${To} with elements of type ${Elem}" +
" based on a collection of type ${From}.")
trait CanBuildFrom[-From, -Elem, +To] {...}
不一樣於java 子類型多態, 這一功能也被成爲 特設多態(ad hoc polymorphism)
scala java 共有 參數化多態(paremetric polymorphism)
case class Address(street: String, city: String)
case class Person(name: String, address: Address)
trait ToJSON {
def toJSON(level: Int = 0): String
val INDENTATION = " "
def indentation(level: Int = 0): (String,String) =
(INDENTATION * level, INDENTATION * (level+1))
}
implicit class AddressToJSON(address: Address) extends ToJSON {
def toJSON(level: Int = 0): String = {
val (outdent, indent) = indentation(level)
s"""{ |${indent}"street": "${address.street}", |${indent}"city": "${address.city}" |$outdent}""".stripMargin
}
}
implicit class PersonToJSON(person: Person) extends ToJSON {
正如上述使用場景所述,隱式在scala中給咱們帶來不少驚喜。經過隱式,咱們也能夠更好的解決上下文處理、邊界處理、類型擦除等問題。
爲什麼不適用簡單類型 + 類型類模式
trait Stringizer[+T] {
def stringize: String
}
implicit class AnyStringizer(a: Any) extends Stringizer[Any] {
def stringize: String = a match {
case s: String => s
case i: Int => (i*10).toString
case f: Float => (f*10.1).toString
case other =>
throw new UnsupportedOperationException(s"Can't stringize $other")
}
}
val list: List[Any] = List(1, 2.2F, "three", 'symbol)
list foreach { (x:Any) =>
try {
println(s"$x: ${x.stringize}")
} catch {
case e: java.lang.UnsupportedOperationException => println(e)
}
}
咱們定義了一個名爲 Stringizer 的抽象體。若是按照以前 ToJSON 示例的作法,咱們會爲全部咱們但願能字符串化的類型建立隱式類。這自己就是一個問題。若是咱們但願處理一組不一樣的類型實例,咱們只能在 list 類型的 map 方法內隱式地傳入一個 Stringizer 實例。
所以,咱們就必須定義一個 AnyStringerize 類,該類知道如何對咱們已知的全部類型進行處理。這些類型甚至還包含用於拋出異常的 default 子句。這種實現方式很是不美觀,同時也違背了面向對象編程中的一條核心規則——你不該該使用 switch 語句對可能發生變化的類型進行判斷。相反,你應該利用多態分發任務,這相似於 toString 方法在 Scala 和 Java 語言中的運做方式。
例如:
假如你爲某一類型定義方法 + ,並試圖將該方法應用到某一不屬於該類型的實例上,
那麼編譯器會調用該實例的 toString 方法,這樣一來便能執行 String 類型的 + 操做(合
並字符串操做)。這能夠解釋某些特定狀況下出現像 String 是錯誤類型的奇怪錯誤
與此同時,若是有必要的話,編譯器會將方法的輸入參數自動組合成一個元組。有時候這
一行爲會給人形成困擾。幸運的是,Scala 2.11 如今會拋出警告信息。
scala> def m(pair:Tuple2[Int,String]) = println(pair)
scala> m(1, "two")
<console>:9: warning: Adapting argument list by creating a 2-tuple:
this may not be what you want.
signature: m(pair: (Int, String)): Unit
given arguments: 1, "two"
after adaptation: m((1, "two"): (Int, String))
m(1,"two")
Scala 會解析那些導入到當前做用域的隱式值(這也無須輸入前綴路徑)。
隱式類 Scala匹配。 將挑選匹配度最高的隱式。舉個例子,若是隱式參數類型是 Foo 類型,而當前做用域中既存在 Foo 類型的隱式值又存在AnyRef 類型的隱式值,那麼 Scala 會挑選類型爲 Foo 的隱式值。