• 一文带你读懂scala中的隐式转换


    1.隐式转换的概念

    隐式转换是将A转换成B,但并不是A真的就成了B,而是A本来的属性仍存在的同时又拥有了B的属性,这使得了A本身不发生变化的同时,扩大了功能,此属于蒙面设计模式。又因为A直接使用了B的功能而不需要对A进行修改,因此此转换是隐式的,使用implicit修饰。所以简单的说隐式转换就是增强类型,扩展功能。

    2.隐式转换适用情况

    隐含转换适用于两种情况:

    1)、如果表达式e是类型S,并且S不符合表达式的期望类型T

    2)、在具有类型S的e的e.m表达中,如果m不表示S的成员

    在第一种情况下,搜索适用于e并且其结果类型符合T的转换c。在第二种情况下,搜索适用于e的转换c,其结果包含名为m的成员。

    3.隐式转换的原理

    当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,用于将类型进行转换,实现二次编译。

    当想调用对象功能时,如果编译错误,那么编译器会尝试在当前作用域范围内查找能调用对应功能的转换规则,这个调用过程是由编译器完成的,所以称之为隐式转换,或称之为自动转换。

    因为隐式转换可能有缺陷,如果不加区别地使用,编译器在编译隐式转换定义时会发出警告。若要关闭警告,请采取以下任何一种操作:

    1)将scala.language.implicitConversions导入隐式转换定义的范围

    2)调用编译器时,加上:-language:implicitConversions

    当编译器应用转换时,不会发出警告。

    因此,隐式解析机制总结如下:

    (1)首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)

    (2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。

    4.隐式转换的作用域

    隐式转换作用域的概念,scala编译器仅考虑作用域之内的隐式转换,要使用某种隐式操作,必须以单一标识符的形式(一种情况例外)将其带入作用域之内。例如:

    1. object TestImplicit {
    2. implicit def doubleToInt(x: Double) = x.toInt
    3. }
    4. object Test {
    5. def main(args: Array[String]): Unit = {
    6. //以单一标识符引进doubleToInt的隐式转换
    7. import TestImplicit._
    8. val i: Int = 2.3
    9. }
    10. }

    单一标识符有一个例外,编译器还将在源类型和目标类型的伴生对象中寻找隐式定义。

    5.隐式转换的若干规则

    1.显示定义规则

    在使用带有隐式参数的函数时,如果没有明确指定与参数类型匹配相同的隐式值,编译器不会通过额外的隐式转换来确定函数的要求

    2.作用于规则

    不管是隐式值,隐式对象,隐式类或隐式转换函数,都必须在当前的作用域使用才能起作用。

    3.无歧义规则

    所谓无歧义指的是,不能存在多个隐式转换使代码合法,如代码中不应该存在两个隐式转换函数能够同时使某一类型转换为另一类型,也不应该存在相同的两个隐式值,主构造函数参数类型以及成员方法等同的两个隐式类。

    4.一次性转换规则

    隐式转换从源类型到目标类型只会经过一次转换,不会经过多次隐式转换达到。

    6.常见的隐式转换类型

    1.隐式转换函数

    1.格式

    implicit def 函数名(参数) ={       //函数体       //返回值  }

    2.例子

    1. class RichFile(file : File){
    2. def read = Source.fromFile(file.getPath()).mkString
    3. }
    4. object Context{
    5. implicit def fileToRich(file:File) = new RichFile(file)
    6. }
    7. object ImplicitDemo {
    8. def main(args: Array[String]): Unit = {
    9. import Context.fileToRich //使用隐式住转换
    10. println(new File("d://aaaaa.txt").read)
    11. }
    12. }

    流程大概是这样的:new File().read-->发现File类中没有read方法-->然后找隐式转换,找到某个方法的参数正好File类型,然后便执行这个隐式函数,得到RichFile类型,该类型中read方法,便继续执行即可,否则运行不通过。再例如:

    1. object ImplicitDemo {
    2. object MyImplicitTypeConversion {
    3. implicit def strToInt(str: String) = str.toInt
    4. }
    5. def main(args: Array[String]) {
    6. //compile error!
    7. //val max = math.max("1", 2);
    8. import MyImplicitTypeConversion.strToInt
    9. val max = math.max("1", 2);//遇到不符合的先检查是否在隐式转换的作用域内
    10. println(max)
    11. }
    12. }

    3.注意事项

    1) 隐式转换函数的函数名可以是任意的与函数名称无关只与函数签名(函数参数和返回值类型)有关,即隐式函数的入参要是编译不通过的类型,返回值要是能正确编译的类型。

    2)如果当前作用域中存在函数签名相同但函数名称不同的两个隐式转换函数,则在进行隐式转换时会报错。

    2.隐式类

    在 Scala2.10 后提供了隐式类,可以使用 implicit 声明类,隐式类的非常强大,同样可以扩展类的功能,在集合中隐式类会发挥重要的作用

    1.格式

    1. implicit class 类名(参数){
    2. //类主体
    3. }

    2.例子

    string中没有bark方法,通过隐式转换,调用对应的方法转换。

    1. object Context{
    2. implicit class BA(val name : String){
    3. def bark=println(s"$name is barking")
    4. }
    5. }
    6. object ImplicitDemo {
    7. def main(args: Array[String]): Unit = {
    8. import Context._
    9. println("dog".bark)//"dog"和BA的参数类型相同,便可扩展BA的功能。
    10. }
    11. }

    再比如3.注意事项

    隐式类的运作方式是:隐式类的主构造函数只能有一个参数(有两个以上并不会报错,但是这个隐式类永远不会被编译器作为隐式类在隐式转化中使用),且这个参数的类型就是将要被转换的目标类型。从语义上这很自然:这个隐式转换类将包裹目标类型,隐式类的所有方法都会自动“附加”到目标类型上。所以:

    1)隐式类的主构造函数参数有且仅有一个!之所以只能有一个参数,是因为隐式转换是将一种类型转换为另外一种类型,源类型与目标类型是一一对应的;

    2)implicit修饰符修饰类时只能存在于“类”或“伴生对象”或“包对象”或“特质”之内,即隐式类不能是顶级的;

    3)在同一作用域内,不能有任何方法、成员或对象与隐式类同名;                

    4)隐式类不能是case class;                                                                

    5)Scala源码中存在的大部分隐式转换都是存在于Predef之中,比如:String->StringOps,Array->ArrayOps等,而Scala.Predef 自动引入到当前作用域。

    3.隐式对象

    1.格式

    implicit class 类名(参数){      //类主体  }

    2.例子

    1. object ImplicitTest extends App{
    2. //定义一个乘法接口
    3. trait Multiplicable[T]{
    4. def multiply(x: T):T
    5. }
    6. //定义一个隐式对象,用于整型数据相乘
    7. implicit object MultiplicableInt extends Multiplicable[Int]{
    8. def multiply(x: Int): Int = x*x
    9. }
    10. //定义一个隐式对象,用于字符串相乘
    11. implicit object MultiplicableString extends Multiplicable[String]{
    12. def multiply(x: String): String = x*2
    13. }
    14. //定义一个函数,函数具有泛型参数
    15. def multiply[T: Multiplicable](x: T): T ={
    16. //implicitly方法,访问隐式对象
    17. val ev = implicitly[Multiplicable[T]] // 会检索作用域内类型为Multiplicable[T]的隐式
    18. //根据具体的类型调用相应的隐式对象中的方法
    19. ev.multiply(x) //固定写法
    20. }
    21. //调用隐式对象 MultiplicableInt 中的方法
    22. println(multiply(5)) // 因为5是Int类型,就会找到Multiplicable[Int],继而就能找到MultiplicableInt extends Multiplicable[Int],然后就能找到这个类中定义的multiply方法
    23. //调用隐式对象 MultiplicableString 中的方法
    24. println(multiply("5"))
    25. }
    26. }

    3.注意事项

    1. 1)def multiply[T: Multiplicable](x: T): T = { //等价于def multiply[T](x:T)(implicit mc:Multiplicable[T]):T={
    2. 而T: Multiplicable就是所谓的Context Bounds,类型参数声明 T : Comparator 就表示存在一个 Comparator[T]类型的隐式值。
    2)def implicitly[T](implicit e: T) = e   是Predef中的方法,用于返回隐式对象。编译器会记录当前上下文里的隐式值,而该方法则可以获得某种类型的隐式值。
    3implicit修饰符修饰对象时不能处于顶层对象级别

    4.隐式参数

    1.格式

    def 函数名(implicit 参数名:类型) : 返回值 = {         //函数体  }

    2.例子

    //修改上面的一个方法//定义一个函数,函数具有泛型参数   def multiply[T](x: T)(implicit ev: Multiplicable[T]): T ={     //根据具体的类型调用相应的隐式对象中的方法     ev.multiply(x)  }

    3.隐式参数使用的常见问题

    非柯里化

    1)当函数没有柯里化时,implicit关键字会作用于函数列表中的的所有参数。       

    2)隐式参数使用时要么全部不指定,要么全部指定,不能只指定部分。

    3)在指定隐式参数时,implicit 关键字只能出现在参数开头。

    柯里化

    4)如果想要实现参数的部分隐式参数,只能使用函数的柯里化,例如:要实现这种形式的函数,def test(x:Int, implicit  y: Double)的形式,必须使用柯里化实现:def test(x: Int)(implicit y: Double);

    5) 柯里化的函数, implicit 关键字只能作用于最后一个参数。否则,不合法;

    6)implicit 关键字在隐式参数中只能出现一次,柯里化的函数也不例外!

    7)匿名函数不能使用隐式参数。如:val prodeuct = (implicit x: Double, y: Double) => x*y  会报错;

    8)柯里化的函数如果有隐式参数,则不能使用其偏函数 。

    如:scala> def product(x: Double)(implicit y: Double) = x*y  product: (x: Double)(implicit y: Double) Double  //定义隐式参数后,便不能使用其偏应用函数  scala> val p1 = product _  <console>:13: error: could not find implicit value for parameter y: Double         val p1=product _                ^  

    5.隐式值

    1.格式

    implicit val 变量名: 类型 = 值

    2.例子

    //定义一个带隐式参数的函数  scala> def sqrt(implicit x: Double) = Math.sqrt(x)  sqrt: (implicit x: Double)Double  //定义一个隐式值  scala> implicit val x:Double = 2.55  x: Double = 2.55  //调用定义的sqrt函数,它将自行调用定义好的隐式值  scala> sqrt  res1: Double = 1.5968719422671311

    3.注意事项

    1)同类型的隐式值只能在作用域内出现一次,即不能在同一个作用域中定义多个相同类型的隐式值。但是不同类型的可以出现多次。

    2)编译器按照隐式参数的类型去寻找对应类型的隐式值,与隐式值的名称无关

    3)implicitly[T]可以检索作用域内的类型为T的隐式值:

    1. implicit val str: String = "alice"
    2. implicit val num: Int = 18
    3. def sayHello()(implicit name: String): Unit = {
    4. println("hello, " + name)
    5. }
    6. //如果参数有默认值,隐式值有衔接高于默认值
    7. def sayHi(implicit name: String = "atguigu"): Unit = {
    8. println("hi, " + name)
    9. }
    10. sayHello
    11. sayHi
    12. // 简便写法
    13. def hiAge(): Unit = {
    14. println("hi, " + implicitly[Int]) //会检索上作用域内类型为T的隐式值
    15. }
    16. · hiAge()
    17. }

  • 相关阅读:
    Qt实战案例(54)——利用QPixmap设计图片透明度
    基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持本地图片上传与回显的功能实现(二)
    07.智慧商城——商品详情页、加入购物车、拦截器封装token
    面试题五:computed的使用
    hadoop3.x集群搭建
    大数据处理技术:MapReduce综合实训
    故障预警 vs 故障分类:哪个更有意义,哪个更具挑战性?
    Edegex Foundry docker和源码安装
    Chapter 6 提升
    白酒:中国的酒文化的传承与发扬
  • 原文地址:https://blog.csdn.net/qq_26442553/article/details/133353170