Scala核心编程_第10章 隐式转换和隐式参数

我们先解释一下显式转换,例如下面:

implicit def ftoInt(d: Double): Int = {
  d.toInt
}

引出隐式转换的实际需要=>指定某些数据类型的相互转化。

隐式函数基本介绍

隐式转换函数是以implicit关键字声明的带有单个参数的函数,函数将会自动应用,将值从一种类型转换为另一种类型。、

object ImplicitDemo01 {
  def main(args: Array[String]): Unit = {

    //编写一个隐式函数转成 Double->Int 转换
    //隐式函数应当在作用域才能生效
    implicit def f1(d:Double): Int = { //底层 生成 f1$1
      d.toInt
    }

    implicit def f2(f:Float): Int = {
      f.toInt
    }

    //报错:这里我们必须保证隐式函数的匹配只能是唯一的.
//    implicit def f3(f1:Float): Int = {
//      f1.toInt
//    }


    val num: Int = 3.5 // 底层编译 f1$1(3.5)
    val num2: Int = 4.5f //
    println("num:"+num.getClass+"="+ num)
    println("num2:"+num2.getClass+"="+ num2)

  }
}

 底层:

import scala.Predef.;
import scala.collection.mutable.StringBuilder;
import scala.runtime.BoxesRunTime;

public final class ImplicitDemo01$
{
  static{ new ();}
  
  public static final  MODULE$;
  private ImplicitDemo01$(){MODULE$ = this;}
  
  private final int f1$1(double d){ return (int)d; }
  
  private final int f2$1(float f){ return (int)f;}
  
  public void main(String[] args)
  {
    int num = f1$1(3.5D);
    int num2 = f2$1(4.5F);
    Predef..MODULE$.println(new StringBuilder().append("num =").append(BoxesRunTime.boxToInteger(num)).toString());
  }
}

隐式转换的注意事项

  1. 隐式转换函数的函数名可以是任意的,隐式转换与函数名称无关,只与函数签名(函数参数类型和返回值类型)有关。
  2. 隐式函数可以有多个(即:隐式函数列表),但是需要保证在当前环境下,只有一个隐式函数能被识别
  3. 隐式转换不能有二义性
  4. 隐式转换不能嵌套使用(或者说递归使用)
object ImplicitNotice {
  def main(args: Array[String]): Unit = {

    implicit def f1(d: Double): Int = {
      d.toInt
      val num2:Int = 2.3 //报错:底层 f1$1(2.3) //f1$1对应的就是f1,就会形成递归
    }

    val num1: Int = 1.1
  

 

案例提高

如果需要为一个类增加一个方法,可以通过隐式转换来实现。(动态增加功能)比如想为MySQL类增加一个delete方法

解决方案

在当前程序中,如果想要给MySQL类增加功能是非常简单的,但是在实际项目中,如果想要增加新的功能就会需要改变源代码,这是很难接受的。

而且违背了软件开发的OCP开发原则 (闭合原则 open close priceple) 。

在这种情况下,可以通过隐式转换函数给类动态添加功能。

如果需要为一个类增加一个方法,可以通过隐式转换来实现。(动态增加功能)比如想为MySQL类增加一个delete方法

object ImplicitDemo02 {

  def main(args: Array[String]): Unit = {

//    //编写一个隐式函数,丰富mySQL功能
    implicit def addDelete(msql:MySQL): DB = {
      new DB
    }

    //创建mysql对象
    val mySQL = new MySQL
    mySQL.insert()
    mySQL.delete() // 编译器工作 分析 addDelete$1(mySQL).delete()
    mySQL.update()

  }
}

class MySQL {
  def insert(): Unit = {
    println("insert")
  }
}

class DB {
  def delete(): Unit = {
    println("delete")
  }

  def update(): Unit = {
    println("update")
  }
}

 

隐式值

基本介绍

隐式值也叫隐式变量,将某个形参变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数。

//小结
//1. 当在程序中,同时有 隐式值,默认值,传值
//2. 编译器的优先级为 传值 > 隐式值 > 默认值
//3. 在隐式值匹配时,不能有二义性
//4. 如果三个 (隐式值,默认值,传值) 一个都没有,就会报错

object ImplicitVal {
  def main(args: Array[String]): Unit = {
    // 隐式变量(值)
    implicit val name: String = "Scala"
//    implicit val name1: String = "World"

    //隐式参数
    def hello(implicit content: String = "jack"): Unit = {
      println("Hello " + content)
    } //调用hello
    hello

    //当同时有implicit 值和默认值,implicit 优先级高
    def hello2(implicit content: String = "jack"): Unit = {
      println("Hello2 " + content)
    } //调用hello
    hello2


    //说明
    //1. 当一个隐式参数匹配不到隐式值,仍然会使用默认值

    implicit val name2: Int = 10
    def hello3(implicit content: Double = 1.23): Unit = {
      println("Hello3 " + content)
    } //调用hello
    hello3 //  hello3 jack

//    //当没有隐式值,没有默认值,又没有传值,就会报错
//    def hello4(implicit content: Double ): Unit = {
//      println("Hello4 " + content)
//    } //调用hello
//    hello4 //  hello3 jack
  }
}

 报错:

  1. 当没有隐式值(或者隐式值匹配不上),没有默认值,又没有传值,就会报错。
  2. 同类型隐式值重复也会报错。

 底层:

隐式函数会对非implicit的形参

我们看到隐式值会对同类型的隐式形参进行赋值,而且隐式函数会对同类型非隐式形参进行转换。

object ImplicitArgs {


  implicit def f1(d: Double): Int = {
    d.toInt
  }

  def main(args: Array[String]): Unit = {
    //    这里也可以
    //    implicit def f1(d:Double): Int = {
    //      d.toInt
    //    }

    def test1(n: Int): Unit = {
      println("n:"+n.getClass+"===>"+n)
    }

    test1(10.1)

  }
}

  

底层代码:

 

 隐式形参

  def main(args: Array[String]): Unit = {

    implicit def implicitToDouble(d: Double): Int = {
      d.toInt
    }
    implicit val age: Double = 10.1
    def test1(implicit n: Double): Unit = {
      println("n:" + n.getClass + "===>" + n)
    }
    test1
  }

 

 

隐式类

基本介绍

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

class MySQL {
  def connect(): Unit = {
    println("MySQL...connect")
  }
}

object ImplicitClassDemo {

  def main(args: Array[String]): Unit = {
    //ImplicitDB会对应生成隐式类,ImplicitDB是一个隐式类, 当我们在该隐式类的作用域范围,创建MySQL实例
    //该隐式类就会生效, 这个工作仍然编译器完成
    implicit class ImplicitDB(val m: MySQL) { //ImplicitClassDemo$DB1$2
      def addSuffix(): Unit = {
        println("addSuffix()====>" + m + " scala")
      }
    }

    //创建一个MySQL实例
    val mysql = new MySQL
    mysql.connect()
    mysql.addSuffix() //研究 如何关联到 ImplicitDB(mysql).addSuffix();
    println("mysql=======>"+mysql.getClass)

  }
  
}

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

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

隐式的转换时机

  1. 当方法中的参数的类型与目标类型不一致时
  2. 当对象调用所在类中不存在的方法或成员时,编译器会自动将对象进行隐式转换(根据类型)

即编译器是如何查找到缺失信息的,解析具有以下两种规则:

  1. 首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
  2. 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下(第二种情况范围广且复杂在使用时,应当尽量避免出现):
    • 如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索。
    • 如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象。
    • 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索。
    • 如果T是个类型注入S#T,那么S和T都会被搜索。
原文地址:https://www.cnblogs.com/wqbin/p/13064402.html