Implicits-进阶

隐式转换的用途:

  • DSL
  • Type evidence
  • 减少代码冗余
  • Type class
  • 编译期 DI
  • 扩展现有库

需要注意的地方:

  • 解决规则会变得困难
  • 自动转换
  • 不要过度使用

隐式转换

一个普通的方法:

def makeString(i:Int):String = s"makeString: ${i.toString}"

以正常方式调用:

makeString(100)

但是如果我们有一个其他非 Int 类型的对象需要作为该方法的参数,则需要提供一个隐式转换,将其他类型转换为需要的 Int 类型:

case class Balance(amount:Int)
val balance = Balance(100)

implicit def balance2Int(balance:Balance):Int = balance.amount
makeString(balance)

JavaConversion & JavaConverters

在与 Java 互操作时,比如我们的方法需要一个 Scala 类型的集合,而原有代码只能提供 Java 类型的集合,这时可以引入 Scala 中预定义的两种类型的隐式转换:

val javaList:java.util.List[Int] = java.util.Array.asList(1,2,3)
def someDefUsingSeq(seq:Seq[Int]) = println(seq)
import scala.collection.JavaConversions._
someDefUsingSeq(javaList)

或者以更好的方式,在转换时显示指定:

import scala.collection.JavaConverters
someDefUsingSeq(javaList.asScala)

隐式视图

如果我们需要包装或扩展一个已有的类型对象:

class StringWrapper(s:String){
  def quoted = s"$s"
}

可以以下面的方式调用:

new StringWrapper("string").quoted

但是并不能以下面的方式调用:

"string".quoted

这时,可以定义一个从StringStringWrapper视图作为隐式转换:

implicit def warpString(s:String):StringWrapper = new StringWrapper(s)
"string".quoted		// "string" 会通过隐式转换自动转换为一个 StringWrapper 对象

视图绑定:废弃。

隐式参数

声明一个带有隐式参数的方法:

def giveMeAnInt(implicit i: Int) = i

可以以正常的方式调用该方法:

giveMeAnInt(1)

以隐式的方式提供参数:

implicit val someInt = 100
giveMeAnInt	// 100

但是如果同时提供多个隐式 Int 类型值,则会报错。

隐式类

假如其他的库已经定义了一个类:

case class Balance(amount:Int)
val balacne = 100

如果这时我们想对它做些操作,比如:

-balance

这时,我们可以创建一个包装类来扩展它:

implicit class RichBalance(val balance:Balance){
  def unary-: Balance = balance.copy(amount = -balance.amount)
}

这种用法主要用于扩展其他已有的库或类型。

隐式声明

作用域

查找优先级:

  • 通过名字,不使用任何前缀,当前作用域
  • 在隐式作用域中:
    • 伴生对象
    • 包对象
      • 源类型的包对象
      • 参数和超类、超特质的包对象

Type class