Scala中的隐式转换
在java中,如果将精度大的数转换成精度小的数,必须使用强制类型转换才能实现。
但是,在scala,可以通过隐式转换的方式,让这种转换无需明显的强制类型转换就能完成。
Scala中的隐式转换包括三类:
- 隐式转换函数
- 隐式类
- 隐式参数和隐式值
隐式函数
当出现类型转换冲突的时候,scala就会寻找隐式转换,不看函数名,只看参数类型和返回值类型
implicit def double2int(double: Double): Int= double.toInt
implicit def double2String(double: Double): String= double.toString
val a:Int = 2536.456789456489
val a1:String = 26.489456
println(a)
println(a1)
结果:
2536
26.489456
隐式函数将来可以给已有的类增加功能!
使用隐式函数扩展 Java中的File类的功能
import java.io.File
import scala.io.Source
object Implicit2 {
def main(args: Array[String]): Unit = {
implicit def file2RichFile(file: File):RichFile = new RichFile(file)
val content:String = new File("C:\\Projects\\IDEAProjects\\IDEAProjects\\ScalaProjects\\src\\o\\l\\scala\\Functions\\Implicit2.scala").readContent
println(content)
}
}
class RichFile(file: File) {
def readContent = {
Source.fromFile(file, "utf-8").mkString
}
}
结果: 将本文件的内容读取出来
package o.l.scala.Functions
import java.io.File
import scala.io.Source
object Implicit2 {
def main(args: Array[String]): Unit = {
implicit def file2RichFile(file: File):RichFile = new RichFile(file)
val content:String = new File("C:\\Projects\\IDEAProjects\\IDEAProjects\\ScalaProjects\\src\\o\\l\\scala\\Functions\\Implicit2.scala").readContent
println(content)
}
}
class RichFile(file: File) {
def readContent = {
Source.fromFile(file, "utf-8").mkString
}
}
Process finished with exit code 0
正常Java中的File类创建的时候,这个File对象只能获取文件的元信息,无法获取文件中的内容。
在上述代码中,通过隐式函数, 将Java中的File类型对象 隐式转换成自己定义的 RichFile对象,然后在通过 自己定义的readContent方法获取文件中的内容,从而从直观上看,File类被扩展了,其中扩展的readContent方法获取文件内容。
当一个File对象调用readContent()方法时,由于原File类中没有这个方法,那个scala就会找有没有隐式转换。输入参数是File类型,转换后的参数类型中存在readContent方法。这样就实现了通过隐式转换函数扩展了File类的功能。
使用隐式转换函数扩展Int
需求: 实现 2 days ago :计算当前日期的两天前的时间
2 days later:计算当前日期的两天后的时间
是不是有些懵逼?
其实上面的需求是下面的缩写
var data:String = 2.days(“ago”)
这里会涉及一些 scala中的 中缀表达式和后缀表达式的知识:
可以参考:Scala中的中缀表达式和后缀表达式
如果想要使用后缀表达式,需要提前导入:import scala.language.postfixOps
class RichDate(dateNum: Int) {
var name:String = "hello"
def days(S: String): String = {
if (S == "ago") {
LocalDate.now().plusDays(-dateNum) toString
} else {
LocalDate.now().plusDays(dateNum).toString
}
}
}
main()方法中:
implicit def Int2Date(datenum: Int) = new RichDate(datenum)
println(2 days "ago")
结果:
2020-11-12
Process finished with exit code 0
隐式类(scala2.1.0新增的特性)
隐式类是隐式转换函数的一个封装,简化隐式转换函数的使用。
从上述隐式转换函数的实现来看,如果想要使用隐式转换函数扩展某个类,首先需要创建一个类,其类的主构造方法接收要扩展的类的对象,在类的里面创建方法,进行这个类对象的功能扩展。最后在定义隐式转换函数,将当前类型的对象转换成我们自定义的类对象,进而可以调用扩展后的方法和属性。
隐式类就是对于上述隐式转换函数的封装,具体实现如下:
- 隐式类不能定义在顶级(即只能定义为内部类)
- 隐式类的主构造方法必须有需要转换的参数
object ImplicitClass {
def main(args: Array[String]): Unit = {
val str: String = 2 days "ago"
println(str)
}
implicit class RichDate(dateInt:Int){
def days(S:String)={
if(S=="ago"){
//TODO 这里使用了后缀表达式
LocalDate.now().plusDays(-dateInt) toString
}
else{
LocalDate.now().plusDays(dateInt) toString
}
}
}
}
结果:
2020-11-12
Process finished with exit code 0
虽然隐式类比较简洁,但是spark中还是隐式转换函数使用的比较多。
隐式参数和隐式值
通常情况下,隐式参数和隐式值要配合着使用。
隐式参数定义就是在函数或者方法的参数列表中增加implicit关键字
object 隐式参数和隐式值 {
def main(args: Array[String]): Unit = {
sum(1,2)
def sum(implicit a:Int, b:Int)={
println(a+b)
}
}
}
结果:
3
隐式参数与隐式值的配合使用
object 隐式参数和隐式值 {
def main(args: Array[String]): Unit = {
implicit var a: Int = 1
implicit var b: Double = 2
sum
def sum(implicit a: Int, b: Double) = {
println(a + b)
}
}
}
结果:
3.0
温馨小贴士:
- 如果函数或者方法的参数列表中有implicit关键字,那么这个参数列表中的所有参数都是隐式参数;
- 隐式值定义完成之后,在调用含有隐式参数的方法或者函数时,可以省略参数;
- 如果想要使用隐式值,方法或者函数调用时必须需要省略小括号,如果加上小括号就会调用默认值
- 隐式参数进行匹配时,只匹配有没有它自身需要类型的隐式值,与隐式值的变量名无关;
- 如果定义了两个或者多个类型相同的隐式值,那么隐式参数就会撒娇了,进而无法进行匹配,这时虽然IDE不会提示有错误,但是编译时会报错。(这里也同样适用于隐式转换函数和隐式类)
如何使一个函数或者方法的参数列表中,既有必要参数又有隐式参数呢?
聪明的你一定会想到这样的形式:def sum1(a:Int, implicit b:Int)
但是 implicit关键字只能定义在参数列表的最前面,无法定义在中间。
如果想要实现一个函数或者方法的参数列表中,既有隐式参数又有必要参数,可以通过我们之前所学的函数柯里化来实现将必要参数和隐式参数同时出现。
这里需要把必要参数定义在前面的几个()内部,需要把隐式参数定义在最后的()中,同一个函数进行柯里化的时候,只能存在一个隐式参数列表
object 隐式参数和隐式值 {
def main(args: Array[String]): Unit = {
implicit var a: Int = 1
implicit var b: Double = 2
sum1(1)(2)
def sum(implicit a: Int, b: Double) = {
println(a + b)
}
def sum1(a:Int)(b:Int)(implicit c:Double): Unit ={
println(a+b+c)
}
}
}
结果:
5.0
隐式值与默认值的区别
object 隐式参数和隐式值 {
def main(args: Array[String]): Unit = {
implicit var a: Int = 1
implicit var b: Double = 2
// sum1(1)(2)
sum2 //TODO 先找隐式值,如果隐式值不存在,则使用默认值
sum2() //TODO 直接使用默认值
def sum(implicit a: Int, b: Double) = {
println(a + b)
}
def sum1(a:Int)(b:Int)(implicit c:Double): Unit ={
println(a+b+c)
}
def sum2(implicit a:Int=10)={
println(a)
}
}
}
结果:
1
10
Tip:
sum2 //TODO 先找隐式值,如果隐式值不存在,则使用默认值
调用函数或者方法时,如果小括号省略,则优先寻找隐式值,如果没有隐式值,则会使用参数的默认值。
sum2() //TODO 直接使用默认值
调用函数或者方法时,如果小括号没有省略,那么参数的值直接使用默认值
隐式对象
implicit object InnerObject {
def printAge(person:Person) :Int = {
println("enter object ImplicitInnerObject")
person.age
}
}
隐式对象不能作为函数或者方法的隐式参数类型,但是可以通过隐式对象传递给方法或者函数,这里可以通过继承的方式来实现。
//定义trait
trait ImplicitInnerTrait {
println("enter trait ImplicitInnerTrait")
def printAge(person:Person) :Int //抽象方法
}
//定义隐式对象
implicit object ImplicitInnerObject extends ImplicitInnerTrait{
def printAge(person:Person) :Int = { //重写方法
println("enter object ImplicitInnerObject")
person.age
}
}
//定义函数,参数包含隐式参数对象
//def callImplic(person:Person)(implicit o:ImplicitInnerObject) = { //错误!
def callImplic(person:Person)(implicit o:ImplicitInnerTrait) = {
println("call implicit")
o.printAge(person)
}
关于隐式对象的说明,这里参考了scala隐式转换
隐式实体的查找路径问题(隐式解析机制)
- 首先会在当前代码的作用域下查找隐式实体(隐式函数,隐式类,隐式对象,隐式值)(一般是这种情况!)
- 如果上述说明的隐式实体查找失败,会继续在隐式参数的类型的作用域里查找。
类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象中。
本作用于下的隐式实体查找
def main(args:Array[String]){
implicit def a2b(aaa:BBB) = {
println("本作用域下的隐式转换")
new AAA()
}
val aaa:AAA = new BBB()
}
class AAA{
}
class BBB{
}
结果会输出:
本作用域下的隐式转换
AAA类的伴生对象作用域下的隐式实体的查找
def main(args:Array[String]){
val aaa:AAA = new BBB()
}
class AAA{
}
object AAA{
//TODO 这里的隐式转换函数的返回值类型必须指定,不指定会报错
implicit def a2b(bbb:BBB):AAA = {
println("AAA伴生对象用域下的隐式转换")
new AAA()
}
}
class BBB{
}
结果会输出:
AAA伴生对象用域下的隐式转换
BBB类的伴生对象同理,但是两者的伴生对象不能同时出现相同的隐式转换。
从幽冥世界召唤隐式值(主动查找隐式值)
通过 implicitly[类型] 可以主动调用隐式值
def main(args: Array[String]): Unit = {
implicit val testNum:Int = 10
def test(implicit a:Int)={
println(a)
}
test(implicitly[Int])
}
结果:
10