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 的隐式值。