Scala中的隐式转换就如现实中的潜规则,对于了解它的人觉得是便利,如鱼得水,对于不了解它的人,便是捉摸不透。

隐式参数类型转换

隐式转换其实我们一直在使用,只是一直没有正式定义过他。比如我们肯定写过这样的代码:

val str = "my age is "+23 //23 会被转为字符串“23”
val num = 3.0*2 //Int 2会被转化为Double 2.0

很多语言都有这种能力,会正确的帮我们把类型进行转化。Scala更近了一步,允许我们制定这种规则。
举一个例子,我们在Scala里要把一些值放到一个Json对象里去。但是Json对象需要里边的值必须为JsValue。

val json = JsonArray(valueToJson(1),valueToJson("abc"),valueToJson(1.5))

我们在代码里不得不写很多将scala 值转化为JsonValue的函数,使得代码看起来非常臃肿。通过下边一个函数我们可以让编译器帮我们自动做这种类型转化:

implicit def valueToJson(obj : Any):JsValue = {
  obj match {
    case i : Int => JsNumber(i)
    case l : Long => JsNumber(l)
    case f : Float => JsNumber(f.toDouble)
    case d : Double => JsNumber(d)
    case s : String => JsString(s)
    case _ => JsNull
  }
}
val json = JsonArray(1,"abc",1.5)

这个函数的名字valueToJson并不重要,重要的是前边的关键字implicit。有了这个关键字,只要能直接调用这个方法的地方,任意类型都可以被自动转化为一个JsValue。关于隐式转换的作用域你需要知道:

  1. 在你定义的隐式转换的作用域内,隐式转换有效。你可以把隐式转换定义在你需要的类的内部,也可以定义在一个单例对象中,在工程其他对方引入这个单例对象。
  2. 编译器除了在当前作用域内寻找隐式转换定义,还会在源对象或者目标对象的伴生对象里寻找隐式转换定义。

上边我们看到的是隐式转换的第一种情况:类型转换,除此之外,Scala里还有其他两种隐式转换:

隐式类

上边的例子是我们的方法期望一个类型的参数A 但是传入的参数类型是B,这里编译器想,好的,我要在作用域里找一个定义的implicit的方法,这个方法接收的参数类型是B,返回的参数类型是A。这个需求是明确的。但是下边这种情况需求就没有这么直白了。
比如我们有这么个需求,我们的程序接收一个服务返回的消息,消息是个String类型的字符串。我们对这个消息经常进行的操作就是判断是不是一个异常信息。为此我们定义了一个类:

class MessageParser(val message:String){
  def isErr=message.contains("Error")||message.contains("Exception")
}

然后在我们的代码里有很多地方都是这样的调用

val message = "Error:you are too handsome!"
val error = new MessageParser(message).isErr

加入我们能把这个我们自己定义的isErr加到String类里就好了。你可能觉得这个想法太疯狂了。但是Scala说,“我觉得OK。”

implicit class MessageParser(val message:String){
  def isErr=message.contains("Error")||message.contains("Exception")
}
val message = "Error:you are too handsome!"
message.isErr

答案就是加在class前加上implicit关键字。这个隐式类比上边看到隐式参数类型转换更复杂,因为参数类型转换明确知道要从类型A转化为类型B。但是这个我们只知道我们要调用isErr这个方法,并不知道哪个类型具有这个方法。编译器会帮我们找到合适的隐式类来构造。但是这让看代码人的就更容易找不到头绪。还有就是隐式类的构造函数只能有一个参数。

隐式参数

我们之前说到过默认参数,它的作用是调用这个方法时如果没有给定某个参数的值,可以用默认值替代,但是这个默认值是固定的。不论何时何地调用这个方法,默认值都是一样的。但是隐式参数在不同的作用域下可以表现不一样。我们看一下这个例子:

class Noodle(){
  private val fullPrice=10                                   //面条原价10元
  def price(implicit disc:Discount)=fullPrice*disc.discount  //注意implicit关键字。价格=原价*折扣,折扣是隐式参数
}
class Discount(val discount:Double)
object Discounts {
  implicit val normalDiscount=new Discount(0.9)              //定义了隐式值,普通用户9折
  implicit val vipDiscount=new Discount(0.7)                 //隐式值,vip用户7折
}
class VIP   {
  import Discounts.vipDiscount                               //在VIP的作用域内引入7折这个隐式值
  def noodleBill()= new Noodle().price
}
class NormalCustomer  {
  import Discounts.normalDiscount
  def noodleBill()= new Noodle().price
}

new VIP().noodleBill()                //Double = 7.0
new NormalCustomer().noodleBill()     //Double = 9.0

可以看到通过作用域的控制,隐式参数可以获得不同的值。虽然折扣是个Double类型的变量,但是我们还是封装了一个Discount类,因为Double是基本类型,定义成隐含参数在作用域内可能会冲突。

隐式转换会让代码简洁,但是也让代码调试和阅读不便。一定要酌情使用。

1 对 “Rethink Scala 之九:隐式转换”的想法;

发表评论

电子邮件地址不会被公开。 必填项已用*标注

%d 博主赞过: