Scala编程——第12章:隐式转换和隐式参数


本章学习隐式转换和隐式参数,隐式转换和隐式参数是Scala两个功能强大的工具,在幕后处理那些很好有价值的工作。属于Scala高级阶段知识,初学者只需了解其用法即可,不用太过深入。

一、隐式转换

1.基本概念

隐式转换指的是那种以implicit关键字声明的带有单个参数函数。这样的函数将被自动应用,将一种类型转为另一种类型

①应用案例

首先看一个简单的案例,定义一个整数类型Int的变量,赋值Double类型:

val num: Int = 4.5  //nun为Int类型,赋值4.5 Double类型

执行结果:
在这里插入图片描述
从执行结果发现,并不能编译通过,不能将Double类型的值 赋值给Int类型。

这时,我们可以使用隐式转换函数优雅的解决上述数据类型转换问题。
首先需要编写一个隐式转换函数Doule2Int:

implicit def Doule2Int(d : Double): Int = d.toInt

执行结果:
在这里插入图片描述
可以看到,val num: Int = 4.5赋值成功。

②源码分析

为了方便反编译,我们创建一个scala源文件:

object DemoImplicit{
	def main(args:Array[String]): Unit = {
		implicit def Double2Int(d: Double): Int  = d.toInt
		val num: Int = 3.5
	}
}

反编译后的代码:

public final class DemoImplicit$
{
    public static DemoImplicit$ MODULE$;
    
    static {
        new DemoImplicit$();
    }
    
    public void main(final String[] args) {
        final int num = Double2Int$1(3.5);
        Predef$.MODULE$.println((Object)BoxesRunTime.boxToInteger(num));
    }
    
    private static final int Double2Int$1(final double d) {
        return (int)d;
    }
    
    private DemoImplicit$() {
        DemoImplicit$.MODULE$ = this;
    }
}

从反编译的源码可以看到val num: Int = 3.5底层调用了Double2Int$1(3.5),而Double2Int$1方法return (int)d,即对传入Double类型的d 做了int强转。

③细节问题
  1. 隐式转换函数的函数名可以是任意的,隐式转换与函数名称无关,只与函数参数类型和返回值类型有关。
  2. 隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有唯一个隐式函数能被识别。
  3. Scala在隐式转换的时候可能有潜在的问题,为了避免在使用隐式函数时出现警告,我们可以添加 import scala.language.implicitConversions
    在这里插入图片描述

2.隐式转换丰富类库功能

隐式转换的另一个作用是:可以丰富类库的功能。比如你希望你使用的某个类有某个方法,但是这个类却没有提供。Java没有办法解决,但Scala允许你定义一个经过丰富的类型,添加你想要的功能。

例如:如果你希望java.io.File类能有个read方法来读取文件:

val contents = new Flie("README").read

我们可以定义一个经过丰富的类型,添加read方法:

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

然后再提供一个隐式转函数来 将原来的File类型 转换到新的RichFile类型:

implicit def file2RichFile(from: File) = new RichFile(from)

这样,我们就可以在File对象上调用read方法了。

除了提供隐式转换函数,我们也可以提供一个隐式转换类:

implicit  class RichFile(val from: File){
	def read = Source.fromFile(from.getPath).mkString
}

3.引入隐式转换

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

  1. 位于或者目标类型的伴生对象中的隐式函数。
  2. 位于当前作用域可以用单个标识符指代隐式函数。

4.隐式转换规则

编译器在何时会尝试隐式转换? 隐式转换在以下三种情况会被考虑

  • 1.当表达式的类型与预期的类型不同时。 例如:val num: Int = 4.5编译器会寻找是否有Double => Int类型的隐式转换函数。
  • 2.当对象访问一个不存在的成员时。
  • 3.当对象调用某个方法, 而方法的参数与传入的参数不匹配时。

对应的,Scala编译器在以下三种情况不会考虑尝试隐式转换:

  • 1.如果代码能够在不适用隐式转换的前提下通过,则不会使用隐式转换。例如:如果 a*b能过通过,就不会尝试a *convert(b)或者 b.convert(a)
  • 2.不会嵌套的执行隐式转换函数。即不会执行 conver1(convert2(a))*b
  • 3.存在二义性的转换函数不会执行只能存在一个唯一确定的转换函数。例如,如果convert1(a)*bconvert2(a)*b都存在且合法,那么在执行a*b时,就不会尝试隐式转换。

二、隐式参数

隐式参数也叫隐式变量,将某个形参变量标记为implicit。在这种情况下,编译器将会查找默认值,提供给本次函数调用。
示例:

case class Delimiters(left: String, right: String)

def quote(what: String)(implicit delims: Delimiters) = {
	delims.left + what + delims.right
}

假设不用隐式参数,我们可以用一个显示的Delimiters对象来调用quote方法:

quote("I Love You")(Delimiters("<<", ">>")) //返回结果:<<I Love You>>

如果我们省略隐式参数列表,这样调用quote方法:

quote("I Love You")

在这种情况下,编译器会查找一个类型为Delimiters的隐式值。 这个隐式值必须是被implicit声明的值

编译器会在以下两个地方查找这样的一个对象:

  • 当前作用域所有用单个标识符指代的满足类型要求的def 和 val
  • 满足要求类型相关联的类型的伴生对象中。相关联的类型包括所要求类型本身,以及它的参数类型。

在本例中,我们就可以使用第②中情况:创建一个伴生对象,然后创建一个Delimiters的类型参数。

object MyDelimiters{
	implicit val quoteDelimites = Delimiters("<<",">>")
}

然后从这个对象中引入所有值或指定值:
import MyDelimiters._或者import MyDelimiters.quoteDelimiters
这样一来执行quote("I Love You")会隐式的获得参数("<<",’’>>’’)提供给quote调用。

使用隐式参数的完整代码:

object DemoImplicit{
	case class Delimiters(left: String, right: String)

	def quote(what: String)(implicit delims: Delimiters) = {
		delims.left + what + delims.right
	}
	
	//定义主函数,方便调用 quote
	def main(args:Array[String]): Unit = {
		import MyDelimiters._  //引入隐式参数
		val str: String =  quote("I Love You") //隐式调用
		println(str)
	}
}
//
object MyDelimiters{
	implicit val quoteDelimites = Delimiters("<<",">>")
}

三、隐式类

在scala2.10后提供了隐式类,可以使用implicit声明类,隐式类的非常强大,同样可以扩展类的功能,比前面使用隐式转换丰富类库功能更加的方便,在集合中隐式类会发挥重要的作用。

隐式类使用有如下几个特点:

  • 隐式类所带的构造参数有且只能有一个
  • 隐式类必须被定义在“类”或“伴生对象”或“包对象”里,即隐式类不能是 顶级的(top-level objects)。
  • 隐式类不能是case class
  • 作用域内不能有与之相同名称的标识符

四、总结

隐式定义是 Scala 的一项强大的、可以浓缩代码的功能。但是值得注意的是:隐式转换如果使用得过于频繁,会让代码变得令人困惑。因此,在添加一个新的隐式转换之前,首先问自己能否通过其他
手段达到相似的效果,比如继承、混人组合或方法重载
。不过,如果所有这些都失败了,而你感觉大量代码仍然是繁复冗长的。那么隐式转换可能恰好能帮到你。

隐式转换还涉及 泛型上下界定等等内容,暂时用不上。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页