Scala隐式转换

隐式转换

在scala语言当中,隐式转换是一项强大的程序语言功能,它不仅能够简化程序设计,也能够使程序具有很强的灵活性。要想更进一步地掌握scala语言,了解其隐式转换的作用与原理是很有必要的,否则很难得以应手地处理日常开发中的问题。

在scala语言中,隐式转换是无处不在的,只不过scala语言为我们隐藏了相应的细节,例如scala中的类继承层次结构中:


它们存在固有的隐式转换,不需要人工进行干预,例如Float在必要情况下自动转换为Double类型

隐式转换函数

所谓隐式转换函数(implicit conversion function)指的是以implicit关键字声明的带有单个参数的函数。正如它的名称所表达的,这样的函数将自动应用,将值从一种类型转换为另一种类型。

下列赋值如果没有隐式转换的话会报错:

scala> val x:Int=3.5
<console>:7: error: type mismatch;
 found   : Double(3.5)
 required: Int
       val x:Int=3.5
                 ^

添加隐式转换函数后可以实现Double类型到Int类型的赋值

scala> implicit def double2Int(x:Double)=x.toInt
warning: there was one feature warning; re-run with -feature for details
double2Int: (x: Double)Int

scala> val x:Int=3.5
x: Int = 3

隐式函数的名称对结构没有影响,即implicit def double2Int(x:Double)=x.toInt函数可以是任何名字,只是采用source2Target这种方式命名函数的意思比较明确,阅读代码的人可以见名知义,增加代码的可读性。

利用隐式转换丰富现有类库的功能

你是否曾希望某个类有某个方法,而这个类的作者却没有提供?举例来说,如果java.io.File类能有个read方法来读取文件,那该多好?val content = new File("readme").read

在Scala中,你可以定义一个类型来丰富已有的类型,提供你想要的功能:

import java.io.File
import scala.io.Source
//RichFile类中定义了Read方法
class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

然后在提供一个隐式转换来将原来的类型转换到这个新的类型:

扫描二维码关注公众号,回复: 1053573 查看本文章
object ImplicitFunction extends App{
  implicit def double2Int(x:Double)=x.toInt
  var x:Int=3.5
  //隐式函数将java.io.File隐式转换为RichFile类
  implicit def file2RichFile(file:File)=new RichFile(file)
  val f=new File("file.log").read
  println(f)
}

隐式转换规则

隐式转换作用范围

Scala会考虑如下的隐式转换函数:

1、位于同文件或者目标类型的伴生对象中的隐式函数。

implicit def double2Int(x:Double)=x.toInt
var x:Int=3.5

隐式转换函数与目标代码在同一个文件当中,也可以将隐式转换集中放置在某个包中,在使用进直接将该包引入即可,例如:

package cn.scala.wf
import java.io.File
import scala.io.Source

//在cn.scala.xtwy包中定义了子包implicitConversion
//然后在object ImplicitConversion中定义所有的引式转换方法
package implicitConversion{
  object ImplicitConversion{
    implicit def double2Int(x:Double)=x.toInt
    implicit def file2RichFile(file:File)=new RichFile(file)
  }

}

class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

object ImplicitFunction extends App{
  //在使用时引入所有的隐式方法
  import cn.scala.wf.implicitConversion.ImplicitConversion._
  var x:Int=3.5


  val f=new File("file.log").read
  println(f)
}

这种方式在scala语言中比较常见,在前面我们也提到,scala会默认帮我们引用Predef对象中所有的方法,Predef中定义了很多隐式转换函数,下面是Predef的部分隐式转换源码:

scala> :implicits -v
/* 78 implicit members imported from scala.Predef */
  /* 48 inherited from scala.Predef */
  implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A]
  implicit def any2Ensuring[A](x: A): Ensuring[A]
  implicit def any2stringadd(x: Any): runtime.StringAdd
  implicit def any2stringfmt(x: Any): runtime.StringFormat
  implicit def boolean2BooleanConflict(x: Boolean): Object
  implicit def byte2ByteConflict(x: Byte): Object
  implicit def char2CharacterConflict(x: Char): Object
  implicit def double2DoubleConflict(x: Double): Object
  implicit def float2FloatConflict(x: Float): Object
  implicit def int2IntegerConflict(x: Int): Object
  implicit def long2LongConflict(x: Long): Object
  implicit def short2ShortConflict(x: Short): Object
..........

2、位于当前作用域可以以单个标识符指代的隐式函数。

发生隐式转换的情况

发生隐式转换的情况,有以下几种:

当方法中参数的类型与实际类型不一致时;

当调用类中不存在的方法或成员时,会自动将对象进行隐式转换;

不会发生隐式转换的情况

编译器可以不在隐式转换的编译通过,则不进行隐式转换,例如

//这里定义了隐式转换函数
scala> implicit def double2Int(x:Double)=x.toInt
warning: there were 1 feature warning(s); re-run with -feature for details
double2Int: (x: Double)Int

//下面几条语句,不需要自己定义隐式转换编译就可以通过
//因此它不会发生前面定义的隐式转换
scala> 3.0*2
res0: Double = 6.0

scala> 2*3.0
res1: Double = 6.0

如果转换存在二义性,则不会发生隐式转换,例如

package implicitConversion{
  object ImplicitConversion{
    implicit def double2Int(x:Double)=x.toInt
    //这里定义了一个隐式转换
    implicit def file2RichFile(file:File)=new RichFile(file)
    //这里又定义了一个隐式转换,目的与前面那个相同
    implicit def file2RichFile2(file:File)=new RichFile(file)
  }

}

class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

隐式转换不会嵌套进行,例如

package cn.scala.w f
import java.io.File
import scala.io.Source

package implicitConversion{
  object ImplicitConversion{
    implicit def double2Int(x:Double)=x.toInt
    implicit def file2RichFile(file:File)=new RichFile(file)
    //implicit def file2RichFile2(file:File)=new RichFile(file)
    implicit def richFile2RichFileAnother(file:RichFile)=new RichFileAnother(file)
  }

}

class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

//RichFileAnother类,里面定义了read2方法
class RichFileAnother(val file:RichFile){
  def read2=file.read
}



object ImplicitFunction extends App{
  import cn.scala.xtwy.implicitConversion.ImplicitConversion._
  var x:Int=3.5

  //不能期望会发生File到RichFile,然后RichFile到RichFileAnthoer的转换。下面的语句会报错
  val f=new File("file.log").read2
  println(f)
}

隐式参数

在一般的函数据定义过程中,需要明确传入函数的参数:

class Student(var name:String){
  //将Student类的信息格式化打印
  def formatStudent(outputFormat:OutputFormat)={
    outputFormat.first+" "+this.name+" "+outputFormat.second
  }
}

class OutputFormat(var first:String,val second:String)

object ImplicitParameter {
  def main(args: Array[String]): Unit = {
    val outputFormat=new OutputFormat("<<",">>")
    println(new Student("john").formatStudent(outputFormat))
  }

如果给函数定义隐式参数的话,则在使用时可以不带参数:

package cn.scala.xtwy
class Student(var name:String){
  //利用柯里化函数的定义方式,将函数的参数利用
  //implicit关键字标识
  //这样的话,在使用的时候可以不给出implicit对应的参数
  def formatStudent()(implicit outputFormat:OutputFormat)={
    outputFormat.first+" "+this.name+" "+outputFormat.second
  }
}

class OutputFormat(var first:String,val second:String)

object ImplicitParameter {
  def main(args: Array[String]): Unit = {
    //程序中定义的变量outputFormat被称隐式值
    implicit val outputFormat=new OutputFormat("<<",">>")
    //在.formatStudent()方法时,编译器会查找类型
    //为OutputFormat的隐式值,本程序中定义的隐式值
    //为outputFormat
    println(new Student("john").formatStudent())
  }
}

隐式参数中的隐式转换

函数中如果存在隐式参数,在使用该函数的时候如果不给定对应的参数,则编译器会自动帮我们搜索相应的隐式值,并将该隐式值作为函数的参数,这里面其实没有涉及到隐式转换,下面将演示如何利用隐式参数进行隐式转换,下面的代码给定的是一个普通的比较函数:

object ImplicitParameter extends App {
//下面的代码不能编译通过
//这里面泛型T没有具体指定,它不能直接使用
//<符号进行比较
  def compare[T](first:T,second:T)={
    if (first < second) 
      first 
    else 
      second
  }
}

上面的代码要想使其编译通过,可以用类型变量界定和视图界定指定其上界为Ordered[T],例如:

object ImplicitParameter extends App {
  //指定T的上界为Ordered[T],所有混入了特质Ordered
  //的类都可以直接的使用<比较符号进行比较
  def compare[T<:Ordered[T]](first:T,second:T)={
    if (first < second) 
      first 
    else 
      second
  }
}

这是一种解决方案,我们还有一种解决方案就是通过隐式参数的隐式转换来实现,代码如下:

object ImplicitParameter extends App {
//下面代码中的(implicit order:T=>Ordered[T])
//给函数compare指定了一个隐式参数
//该隐式参数是一个隐式转换
  def compare[T](first:T,second:T)(implicit order:T=>Ordered[T])={
    if (first > second) 
      first 
    else 
      second
  }
  println(compare("A","B"))
}

隐式转换问题梳理

1、多次隐式转换问题 

隐式转换从源类型到目标类型不会多次进行,也即源类型到目标类型的转换只会进行一次。

注意这里指的是源类型到目标类型的转换只会进行一次,并不是说不存在多次隐式转换,在一般的方法调用过程中可能会出现多次隐式转换,例如:

class ClassA {
  override def toString() = "This is Class A"
}
class ClassB {
  override def toString() = "This is Class B"
}
class ClassC {
  override def toString() = "This is  ClassC"
  def printC(c: ClassC) = println(c)
}
class ClassD

object ImplicitWhole extends App {
  implicit def B2C(b: ClassB) = {
    println("B2C")
    new ClassC
  }
  implicit def D2C(d: ClassD) = {
    println("D2C")
    new ClassC
  }
  //下面的代码会进行两次隐式转换
  //因为ClassD中并没有printC方法
  //因为它会隐式转换为ClassC(这是第一次,D2C)
  //然后调用printC方法
  //但是printC方法只接受ClassC类型的参数
  //然而传入的参数类型是ClassB
  //类型不匹配,从而又发生了一次隐式转地换(这是第二次,B2C)
  //从而最终实现了方法的调用
  new ClassD().printC(new ClassB)
}

2、要不要用隐式转换的问题

从上述代码中可以看到,隐式转换功能很强大,但同时也带来了程序复杂性性问题,在一个程序中如果大量运用隐式转换,特别是涉及到多次隐式转换时,会使代码理解起来变得比较困难,那到底要不要用隐式转换呢?下面给出我自己开发实践中的部分总结,供大家参考: 
1 即使你能轻松驾驭scala语言中的隐式转换,能不用隐式转换就尽量不用 
2 如果一定要用,在涉及多次隐式转换时,必须要说服自己这样做的合理性 
3 如果只是炫耀自己的scala语言能力,请大胆使用


参考:

Scala入门到精通——第十八节 隐式转换与隐式参数(一

Scala入门到精通——第十九节 隐式转换与隐式参数(二)

快学Scala;


猜你喜欢

转载自blog.csdn.net/oitebody/article/details/79874421