您的位置:首页 > 其它

scala使用implicit实现扩展已有功能

2017-09-06 14:43 477 查看
一般用在2种情况下,一种是扩展已有类的功能,特别是使用第三方的jar包中类;另一种是函数的隐式参数。
举例来说明

1. 扩展类的功能

假设该类是第三方jar包中的类

class A (val data:Int){
...
}


此时我们希望扩展该类的功能,增加类A的数据显示功能

implicit class B(a:A) {
def show {
println(a.data)
}
}


此时我们扩展了类A功能,可以如下使用。

val a = new A(2)
a.show()


2. 函数隐式参数

def sorted[B >: A](implicit ord : scala.math.Ordering[B]) : Repr = { /* compiled code */ }


上述是SeqLike的方法,我们针对序列进行排序的话,假设序列存放的类型是B,那么我们需要sorted的函数需要一个隐式的参数,该参数为scala.math.Ordering[B]对象

implict val ord = new scala.math.Ordering[B]{
def compare(x : B, y : B) :Int = { /* compiled code */ }
}


这样对于任何类型的序列,只要实现Ordering特质,即可以针对该类型的序列进行排序。

我们来看一个例子:

def display(input:String):Unit = println(input)

我们可以看到,
display
函数的定义只是接受
String
类型的入参,因此调用
display("any string")
这样的函数是没问题的。但是如果调用
display(1)
这样的入参不是String类型的话,编译会出错的。

如果我们想让display函数也能够支持Int类型的入参的话,除了我们重新定一个
def display(input:Int):Unit = println(input)
这样的函数以外,我们还可以在相同的作用域内用
implicit
关键字定义一个隐式转换函数,示例代码如下:

object ImplicitDemo {

def display(input:String):Unit = println(input)

implicit def typeConvertor(input:Int):String = input.toString

implicit def typeConvertor(input:Boolean):String = if(input) "true" else "false"

//  implicit def booleanTypeConvertor(input:Boolean):String = if(input) "true" else "false"

def main(args: Array[String]): Unit = {
display("1212")
display(12)
display(true)
}

}

我们定义了2个隐式转换函数:

implicit def typeConvertor(input:Int):String = input.toString

implicit def typeConvertor(input:Boolean):String = if(input) "true" else "false"

这样
display
函数就可以接受String、Int、Boolean类型的入参了。注意到上面我们的例子中注释的那一行,如果去掉注释的那一行的话,会在运行的时候出现二义性:

Error:(18, 13) type mismatch;
found   : Boolean(true)
required: String
Note that implicit conversions are not applicable because they are ambiguous:
both method typeConvertor in object ImplicitDemo of type (input: Boolean)String
and method booleanTypeConvertor in object ImplicitDemo of type (input: Boolean)String
are possible conversion functions from Boolean(true) to String
display(true)
^

得出的结论是:

隐式转换函数是指在同一个作用域下面,一个给定输入类型并自动转换为指定返回类型的函数,这个函数和函数名字无关,和入参名字无关,只和入参类型以及返回类型有关。注意是同一个作用域。

implicit的应用

我们可以随便的打开scala函数的一些内置定义,比如我们最常用的map函数中->符号,看起来很像php等语言。

但实际上->确实是一个ArrowAssoc类的方法,它位于scala源码中的Predef.scala中。下面是这个类的定义:

final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal {
// `__leftOfArrow` must be a public val to allow inlining. The val
// used to be called `x`, but now goes by `__leftOfArrow`, as that
// reduces the chances of a user's writing `foo.__leftOfArrow` and
// being confused why they get an ambiguous implicit conversion
// error. (`foo.x` used to produce this error since both
// any2Ensuring and any2ArrowAssoc pimped an `x` onto everything)
@deprecated("Use `__leftOfArrow` instead", "2.10.0")
def x = __leftOfArrow

@inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y)
def →[B](y: B): Tuple2[A, B] = ->(y)
}
@inline implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)

我们看到
def ->[B] (y :B)
返回的其实是一个
Tuple2[A,B]
类型。

我们定义一个Map:

scala> val mp = Map(1->"game1",2->"game_2")
mp: scala.collection.immutable.Map[Int,String] = Map(1 -> game1, 2 -> game_2)

这里
1->"game1"
其实是
1.->("game_1")
的简写。

这里怎么能让整数类型1能有->方法呢。

这里其实
any2ArrowAssoc
隐式函数起作用了,这里接受的参数[A]是泛型的,所以int也不例外。

调用的是:将整型的1 implicit转换为 ArrowAssoc(1)

看下构造方法,将1当作
__leftOfArrow
传入。

->方法的真正实现是生产一个Tuple2类型的对象
(__leftOfArrow,y )
等价于
(1, "game_id")


这就是一个典型的隐式转换应用。

其它还有很多类似的隐式转换,都在Predef.scala中:

例如:Int,Long,Double都是AnyVal的子类,这三个类型之间没有继承的关系,不能直接相互转换。

在Java里,我们声明Long的时候要在末尾加上一个L,来声明它是long。

但在scala里,我们不需要考虑那么多,只需要:

scala> val l:Long = 10
l: Long = 10

这就是implicit函数做到的,这也是scala类型推断的一部分,灵活,简洁。

其实这里调用是:

val l : Long = int2long(10)

更牛逼的功能

为现有的类库增加功能的一种方式,用java的话,只能用工具类或者继承的方式来实现,而在scala则还可以采用隐式转化的方式来实现。

隐式参数

看一个例子再说:

object ImplictDemo {

object Context{
implicit val ccc:String = "implicit"
}

object Param{
def print(content:String)(implicit prefix:String){
println(prefix+":"+content)
}
}

def main(args: Array[String]) {
Param.print("jack")("hello")

import Context._
Param.print("jack")
}

}

程序运行结果为:

hello:jack
implicit:jack

隐式转换扩展

import java.io.File

import scala.io.Source

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

object Context{
implicit def file2RichFile(f:File)= new RichFile(f)
}

object ImplictDemo {

def main(args: Array[String]) {
import Context.file2RichFile
println(new File("f:\\create.sql").read)
}

}

上面的代码中调用的read方法其实就是RichFile中定义的read方法。

最后的总结:

记住隐式转换函数的同一个scop中不能存在参数和返回值完全相同的2个implicit函数。
隐式转换函数只在意 输入类型,返回类型。
隐式转换是scala的语法灵活和简洁的重要组成部分
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐