Skip to content

隐式转换

在之前的类型学习中,我们已经学习了自动类型转换,精度小的类型可以自动转换为精度大的类型,这个转换过程无需开发人员参与,由编译器自动完成,这个转换操作我们称之为隐式转换。
在其他的场合,隐式转换也起到了非常重要的作用。如Scala在程序编译错误时,可以通过隐式转换中类型转换机制尝试进行二次编译,将本身错误无法编译通过的代码通过类型转换后编译通过。慢慢地,这也形成了一种扩展功能的转换机制。
Alt text

1. 隐式函数

使用implicit关键字声明的函数称之为隐式函数。这样可以在不需改任何代码的情况下,扩展某个类的功能。
比如

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

    // 转换函数
    implicit def transformAge(age: Double): Int = {
        age.toInt
    }
    val i:Int = 10.5
    println(i)
}

运行结果:
Alt text

2. 隐式参数&隐式变量

普通方法或者函数中的参数使用implicit关键字声明为隐式参数,调用该方法时,编译器会在相应的作用域寻找符合条件的隐式变量, 隐式变量就是被implicit关键字修饰的变量,没有找到就正常普通函数调用。

scala
def main(args: Array[String]): Unit = {
    // 隐式参数
    def reg(name:String)(implicit password:String="000000"):Unit={
        println(s"name: $name, password: $password")
    }
    reg("jack")("111111")
    reg("maomao")

    println("***********************")
    // 隐式参数需要配合隐式变量一起使用
    implicit val pass = "123123"
    reg("tuanzi")
}

运行结果:
Alt text

3. 隐式类

3.1 隐式类概述

在Scala2.10之后提供了隐式类,可以使用implicit声明类。

  1. 在没有隐式类之前,如果需要扩展类的功能,遵从OCP原则的话,可以这么实现:
scala
def main(args: Array[String]): Unit = {
    // User类和UserExt的隐式转换,需要构造他们的关系
    implicit def transformUser(user: User): UserExt = {
        new UserExt()
    }
    val user = new User()
    user.insertUser()
    // 利用class+function,实现扩展类的功能
    user.updateUser()
}

class UserExt() {
    def updateUser(): Unit = {
        println("updateUser")
    }
}

class User {
    def insertUser(): Unit = {
        println("insertUser")
    }
}

运行结果:
Alt text 2. 使用隐式类实现, 代码更加简洁

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

    val user = new User()
    user.insertUser()
    user.updateUser()
}

// 使用隐式类,编译器自动生成transformUser类似方法,实现绑定关系进行互转
// 值得注意的是隐式类的构造函数里的参数只能有一个。
implicit class UserExt(user: User) {
    def updateUser(): Unit = {
        println("updateUser")
    }
}

class User {
    def insertUser(): Unit = {
        println("insertUser")
    }
}
object User{
    // 在伴生对象内,定义隐式类
    // implicit class UserExt(user: User) {
    //     def updateUser(): Unit = {
    //         println("updateUser")
    //     }
    // }
}

3.2 隐式类定义的位置

在隐式转换二次编译的时候,Scala编译器根据隐式类生成隐式转换的逻辑,前提需要知道要用到的隐式类的位置,才能够自动生成转换的逻辑,如果不明确限制查找范围,Scala编译器的效率可想而知,java中的包就要扫描很久。

  1. 隐式类的定义的位置:
  • 当前类的内部, 隐式类不能定义为最外层对象
  • 父类,父类的伴生对象
  • 特质,特质的伴生对象
  • 包对象
    比较特殊的是包对象,将隐式类放到包对象过程如下:
    创建包对象:
    Alt text
scala
object TransformDemo6 {
    def main(args: Array[String]): Unit = {

        val user = new User()
        user.insertUser()
        user.updateUser()
    }

    class User {
        def insertUser(): Unit = {
            println("insertUser")
        }
    }

}
scala
package object transformData {
    implicit class UserExt(user: User) {
        def updateUser(): Unit = {
            println("updateUser")
        }
    }
}

运行结果:
Alt text

总结

我们通过隐式函数,隐式参数,隐式类实现隐式转换的不同场景,底层也就是控制编译器的编译逻辑,这在Java中是不存在的操作,Scala比Java更加灵活。Scala的源码中应用很多隐式函数,比如可以参看Predef.scala源码:
Alt text 又比如隐式参数的应用:

scala
def main(args: Array[String]): Unit = {
    val list: List[Int] = List(3, 5, 1, 8, 2, 7)
    // sortBy有两个参数列表,其中第二个表示隐式参数,
    // 如果调用的时候,没有使用第二个参数列表,那么编译器会自动查找排序规则
    // 今后遇到含有隐式参数的代码,编译器提示(...)错误,就表示隐式变量没有找到
    println(list.sortBy(identity))
    println(list.sortBy(identity)(Ordering.Int.reverse))
}