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

本节主要内容

  1. 隐式转换简介
  2. 隐式转换函数
  3. 隐式转换规则
  4. 隐式参数

1. 隐式转换简介

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

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

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

在前一讲的视图界定中我们也提到,视图界定可以跨越类层次结构进行,它背后的实现原理就是隐式转换,例如Int类型会视图界定中会自动转换成RichInt,而RichInt实现了Comparable接口,当然这里面的隐式转换也是scala语言为我们设计好的

本节将对隐式转换中的隐式转换函数、隐式转换规则、隐式参数进行介绍,使大家明白如何自己实现隐式转换操作。

2. 隐式转换函数

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

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

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

//定义了一个隐式函数double2Int,将输入的参数
//从Double类型转换到Int类型
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
//定义完隐式转换后,便可以直接将Double类型赋值给Int类型
scala> val x:Int=3.5
x: Int = 3

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

隐式转换功能十分强大,可以快速地扩展现有类库的功能,例如下面的代码:

package cn.scala.xtwy

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

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)
}

3. 隐式转换规则

隐式转换可以定义在目标文件当中,例如

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

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

package cn.scala.xtwy
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.xtwy.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

 //....................

那什么时候会发生隐式转换呢?主要有以下几种情况:
1 当方法中参数的类型与实际类型不一致时,例如

def f(x:Int)=x
//方法中输入的参数类型与实际类型不一致,此时会发生隐式转换
//double类型会转换为Int类型,再进行方法的执行
f(3.14)

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

package cn.scala.xtwy

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

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)
  //File类的对象并不存在read方法,此时便会发生隐式转换
  //将File类转换成RichFile
  val f=new File("file.log").read
  println(f)
}

前面我们讲了什么情况下会发生隐式转换,下面我们讲一下什么时候不会发生隐式转换:

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

//这里定义了隐式转换函数
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

scala> 2*3.7
res2: Double = 7.4

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

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
}

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

  //下面这条语句在编译时会出错,提示信息如下:
  //type mismatch; found : java.io.File required:
  // ?{def read: ?} Note that implicit conversions
  //are not applicable because they are ambiguous:
  //both method file2RichFile in object
  //ImplicitConversion of type (file:
  //java.io.File)cn.scala.xtwy.RichFile and method
  //file2RichFile2 in object ImplicitConversion of
  //type (file: java.io.File)cn.scala.xtwy.RichFile
  //are possible conversion functions from java.io.File to ?{def read: ?}
value read is not a member of java.io.File

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

编译提示隐式转换存在二义性(ambiguous)

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

package cn.scala.xtwy
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,然后RifchFile到
  //RichFileAnthoer的转换
  val f=new File("file.log").read2
  println(f)
}

理解了这些规则之后,在使用隐式转换时才能够得心应手

4. 隐式参数

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

package cn.scala.xtwy

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))
  }
}
//执行结果
//<< john >>

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

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())
  }
}

添加公众微信号,可以了解更多最新Spark、Scala相关技术资讯

时间: 2024-11-02 11:02:17

Scala入门到精通——第十八节 隐式转换与隐式参数(一)的相关文章

Scala入门到精通——第二十八节 Scala与JAVA互操作

本节主要内容 JAVA中调用Scala类 Scala中调用JAVA类 Scala类型参数与JAVA泛型互操作 Scala与Java间的异常处理互操作 1. JAVA中调用Scala类 Java可以直接操作纵Scala类,如同Scala直接使用Java中的类一样,例如: //在Person.scala文件中定义Scala语法的Person类 package cn.scala.xtwy.scalaToJava class Person(val name:String,val age:Int) //伴

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

作者:摇摆少年梦 配套视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 隐式参数中的隐式转换 函数中隐式参数使用概要 隐式转换问题梳理 1. 隐式参数中的隐式转换 前一讲中,我们提到函数中如果存在隐式参数,在使用该函数的时候如果不给定对应的参数,则编译器会自动帮我们搜索相应的隐式值,并将该隐式值作为函数的参数,这里面其实没有涉及到隐式转换,本节将演示如何利用隐式参数进行隐式转换,下面的代码给定的是一个普通的比较函数: object ImplicitP

Scala入门到精通——第十三节 高阶函数

本节主要内容 高阶函数简介 Scala中的常用高阶函数 SAM转换 函数柯里化 部分应用函数 1. 高阶函数简介 高阶函数主要有两种:一种是将一个函数当做另外一个函数的参数(即函数参数):另外一种是返回值是函数的函数.这两种在本教程的第五节 函数与闭包中已经有所涉及,这里简单地回顾一下: (1)函数参数 //函数参数,即传入另一个函数的参数是函数 //((Int)=>String)=>String scala> def convertIntToString(f:(Int)=>Str

Scala入门到精通——第二十节 类型参数(二)

本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对scala中的Ordering与Ordered之间的关联与区别进行讲解,先看Ordering.Ordered的类继承层次体系: 通过上面两个图可以看到,Ordering混入了java中的Comparator接口,而Ordered混入了java的Comparable接口,我们知道java中的Comparator是一

Scala入门到精通——第二十四节 高级类型 (三)

作者:摆摆少年梦 视频地址:http://blog.csdn.net/wsscy2004/article/details/38440247 本节主要内容 Type Specialization Manifest.TypeTag.ClassTag Scala类型系统总结 在scala中,类(class)与类型(type)是两个不一样的概念.我们知道类是对同一类型数据的抽象,而类型则更具体.比如定义class List[T] {}, 可以有List[Int] 和 List[String]等具体类型,

Scala入门到精通——第十四节 Case Class与模式匹配(一)

本节主要内容 模式匹配入门 Case Class简介 Case Class进阶 1. 模式匹配入门 在java语言中存在switch语句,例如: //下面的代码演示了java中switch语句的使用 public class SwitchDemo { public static void main(String[] args) { for(int i = 0; i < 100; i++) { switch (i) { case 10:System.out.println("10"

Scala入门到精通——第二十九节 Scala数据库编程

本节主要内容 Scala Maven工程的创建 Scala JDBC方式访问MySQL Slick简介 Slick数据库编程实战 SQL与Slick相互转换 本课程在多数内容是在官方教程上修改而来的,官方给的例子是H2数据库上的,经过本人改造,用在MySQL数据库上,官方教程地址:http://slick.typesafe.com/doc/2.1.0/sql-to-slick.html 1. Scala Maven工程的创建 本节的工程项目采用的是Maven Project,在POM.xml文件

Scala入门到精通——第十五节 Case Class与模式匹配(二)

本节主要内容 模式匹配的类型 for控制结构中的模式匹配 option类型模式匹配 1. 模式的类型 1 常量模式 object ConstantPattern{ def main(args: Array[String]): Unit = { //注意,下面定义的是一个函数 //函数的返回值利用的是模式匹配后的结果作为其返回值 //还需要注意的是函数定义在main方法中 //也即scala语言可以在一个函数中定义另外一个函数 def patternShow(x:Any)=x match { ca

Scala入门到精通——第十六节 泛型与注解

本节主要内容 泛型(Generic Type)简介 注解(Annotation)简介 注解常用场景 1. 泛型(Generic Type)简介 泛型用于指定方法或类可以接受任意类型参数,参数在实际使用时才被确定,泛型可以有效地增强程序的适用性,使用泛型可以使得类或方法具有更强的通用性.泛型的典型应用场景是集合及集合中的方法参数,可以说同java一样,scala中泛型无处不在,具体可以查看scala的api 1 泛型类 //单个泛型参数的使用情况 class Person[T](var name: