scala implicit关键字详解(隐式转换函数、隐式类、隐式参数、隐式值)
来源:互联网 发布:沙发品牌 知乎 编辑:程序博客网 时间:2024/06/05 15:21
scala implicit关键字详解(隐式转换函数、隐式类、隐式参数、隐式值)
一、Overview
implicit
是scala中的一个关键字,关于它有着丰富的用法,使得scala更灵活和容易扩展。截止目前,scala已经来到了2.12.3版本,本篇文章把目前scala支持的implicit
用法作了较为全面的整理,以方便在阅读scala源码的时候稍作参考。文中全部的代码和测试结果均以scala2.12.3为准。
闲话不多讲,目前可以见到的implict
用法有如下几种:
1.隐式转换函数
implicit def int2str(x:Int):String = x.toString
2.隐式类
implicit class Box(x: Int) { }
3.隐式参数
def compare[T](x:T,y:T)(implicit ordered: Ordering[T]):Int = { ordered.compare(x,y)}
4.隐式值
implicit val x: Int = 0
5.隐式对象
implicit object obj {}
6.context bound
def compare2[T: Ordering](x: T, y: T) = { val ord = implicitly[Ordering[T]] ord.compare(x, y)}
先不用着急理解这些代码的含义,下面会逐一讲解。
二、隐式转换函数
首先来看隐式转换函数。
implicit def int2str(x:Int):String = x.toString
这段代码声明了一个函数int2str
,它与正常函数唯一的区别在于前面多出的implicit
关键字。这里的implicit
就是它字面的含义——隐式,它告诉编译器,这个函数是一个隐式转换函数,能够把Int
类型的值转换成String
类型的值。
这种隐式转换的意义在于,如果在进行一个对Int
类型的操作时不合法,编译器会在当前作用域寻找合适的隐式转换,来尝试使这种操作合法。隐式转换发生在这两种情景:
e
是一个S
类型的表达式,而需要的却是T
类型,编译器会寻找S=>T
的隐式转换e
是一个S
类型的表达式,使用点号访问e.m
时,m
不是类型S
的成员,编译器会寻找合适的隐式转换使e.m
合法
隐式转换最常用的用途就是扩展已有的类,在不修改原有类的基础上为其添加新的方法、成员。
例如上面的这个函数,在为Int
类型定义好到String
类型的隐式转换后,所有String
类型支持的操作都可以直接在Int
类型的值上使用:
10.concat("hello")10.length
接受String
类型的函数也可以接受Int
类型:
def hi(x:String) = println("hi"+x)hi(123)
需要注意:
1.对于隐式转换函数,编译器最关心的是它的类型签名,即它将哪一种类型转换到另一种类型,也就是说它应该接受只一个参数,对于接受多参数的隐式函数来说就没有隐式转换的功能了。
implicit def int2str(x:Int):String = x.toString // 正确implicit def int2str(x:Int,y:Int):String = x.toString // 错误
2.不支持嵌套的隐式转换
class A{ def hi = println("hi")}implicit def int2str(x:Int):String = x.toString implicit def str2A(x:Int,y:Int):A = new A"str".hi // 正确1.hi // 错误
3.不能存在二义性,即同一个作用域不能定义两个相同类型的隐式转换函数,这样编译器将无法决定使用哪个转换
/* 错误-- */implicit def int2str(x:Int):String = x.toString implicit def anotherInt2str(x:Int):A = x.toString/* --错误 */
4.代码能够在不使用隐式转换的前提下能编译通过,就不会进行隐式转换
三、隐式类
前面提到,隐式转换最重要的应用是扩展已存在的类,它的功能和c#中的扩展方法很类似。比如我们想对已有的Int
类型添加一个sayhi
的方法,可以这样做:
class SayhiImpl(ivalue:Int) { val value:Int = ivalue def sayhi = println(s"Hi $value!")}implicit def int2Sayhi(x:Int) = new SayhiImpl(x)
那么调用123.sayhi
,将会输出:Hi 123!
。
即我们先实现一个支持sayhi
方法的类,再写一个隐式转换函数,使得Int
类也支持sayhi
。但是这种写法过于啰嗦了,可以使用隐式类实现等价的功能:
implicit class SayhiImpl(ivalue:Int) { val value:Int = ivalue def sayhi = println(s"Hi $value!")}123.sayhi //合法
隐式类就是在类定义前加一个implicit
关键字,这表示它的构造函数是一个隐式转换函数,能够将参数的类型转换成自己的类型,在这里就是构造函数SayhiImpl(ivalue:Int)
定义了Int
到SayhiImpl
的隐式转换。
在使用隐式类时需要注意以下限制条件,这里直接搬运官网的文档:
1.只能在别的trait/类/对象内部定义。
object Helpers { implicit class RichInt(x: Int) // 正确! } implicit class RichDouble(x: Double) // 错误!
2.构造函数只能携带一个非隐式参数。
implicit class RichDate(date: java.util.Date) // 正确! implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误! implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确!
虽然我们可以创建带有多个非隐式参数的隐式类,但这些类无法用于隐式转换。
3.implict关键字不能用于case类
implicit case class Baz(x: Int) // 错误!
四、隐式参数
看最开始的例子:
def compare[T](x:T,y:T)(implicit ordered: Ordering[T]):Int = { ordered.compare(x,y)}
在函数定义的时候,支持在最后一组参数使用 implicit
,表明这是一组隐式参数。在调用该函数的时候,可以不用传递隐式参数,而编译器会自动寻找一个implict
标记过的合适的值作为该参数。
例如上面的函数,调用compare
时不需要显式提供ordered
,而只需要直接compare(1,2)
这样使用即可。
再举一个例子:
object Test{ trait Adder[T] { def add(x:T,y:T):T } implicit val a = new Adder[Int] { override def add(x: Int, y: Int): Int = x+y } def addTest(x:Int,y:Int)(implicit adder: Adder[Int]) = { adder.add(x,y) } addTest(1,2) // 正确, = 3 addTest(1,2)(a) // 正确, = 3 addTest(1,2)(new Adder[Int] { override def add(x: Int, y: Int): Int = x-y }) // 同样正确, = -1}
Adder
是一个trait,它定义了add
抽象方法要求子类必须实现。
addTest
函数拥有一个Adder[Int]
类型的隐式参数。
在当前作用域里存在一个Adder[Int]
类型的隐式值implicit val a
。
在调用addTest
时,编译器可以找到implicit
标记过的a
,所以我们不必传递隐式参数而是直接调用addTest(1,2)
。而如果你想要传递隐式参数的话,你也可以自定义一个传给它,像后两个调用所做的一样。
五、隐式值和隐式对象
最开始的示例代码有,
隐式值:
implicit val x: Int = 0
隐式对象:
implicit object obj {}
上面提到过,在调用含有隐式参数的函数时,编译器会自动寻找合适的隐式值当做隐式参数,而只有用implict
标记过的值、对象、函数才能被找到。
例如自动寻找隐式对象:
implicit object Obj { def hello(s:String) = println(s"Hello $s!") } def test2(s:String)(implicit o: Obj.type ) = { o.hello(s) } test2("world") // 输出Hello world!
自动寻找隐式函数:
implicit def int2str(x: Int): String = x.toString def test1(x: Int, func: String => Unit) (implicit helper: Int => String) = { func("\"" + helper(x) + "\"") } test1(12, println) // 打印出"12"
六、context bound
最后来看隐式作为泛型类型的限制:
def compare2[T: Ordering](x: T, y: T) = { val ord = implicitly[Ordering[T]] ord.compare(x, y)}
上面compare2
是一个泛型函数,其有一个类型参数T
,在这里T:Ordering
对T
类型做出了限制,要求必须存在一个Ordering[T]
类型的隐式值,这种限制就叫做context bound。
这其实是隐式参数的语法糖,它等价于:
def compare2[T](x: T, y: T)(implicit ord:Ordering[T]) = { ord.compare(x, y)}
注意到前面的函数体里用到了implicitly
函数。这是因为在使用[T: Ordering]
这样的类型限制时,我们没有能接触到具体的Ordering[T]
类型的隐式值ord
,这时候调用implicitly
函数,就可以拿到这个隐式值,进而进行下一步操作了。没有这个函数的话,你需要这样写:
def compare2[T: Ordering](x: T, y: T) = { def helper(implicit ord:Ordering[T]) == ord.compare(x, y) helper}
七、隐式解析机制
最后加一点比较深入一点的内容,看一下隐式值的寻找机制,引用自这篇博客:
即编译器是如何查找到缺失信息的,解析具有以下两种规则:
1 .首先会在当前代码作用域下查找隐式实体(隐式方法 隐式类 隐式对象)
2.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下:
(1)如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索
(2)如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的
伴生对象和String的伴生对象(3) 如果T是一个单例类型p.T,即T是属于某个p对象内,那么这个p对象也会被搜索
(4) 如果T是个类型注入S#T,那么S和T都会被搜索
- scala implicit关键字详解(隐式转换函数、隐式类、隐式参数、隐式值)
- 【Spark-Scala语法】implicit(隐式转换)关键字详解
- scala 隐式详解(implicit关键字)
- scala隐式参数(implicit)
- scala隐式参数(implicit)
- scala隐式参数(implicit)
- scala的implicit关键字----不仅仅是隐式类型转换
- scala implicit 隐式转换
- scala implicit隐式转换
- Scala implicit隐式转换
- scala implicit 隐式转换
- scala implicit 隐式转换
- scala implicit 隐式转换
- scala implicit 隐式转换
- scala隐式转换implicit
- 【scala 笔记(9)】 隐式转换 和 隐式参数-- implicit
- scala implicit隐式转化与隐式参数
- scala学习笔记2-隐式转换implicit
- 三极管详细学习二
- Retrofit2+RxJava+OkHttp项目直接使用(简单快捷)
- 文件批量转码Shell脚本实现(这里以gbk18030转utf8为例)
- 数据库连接池性能比对
- 微信公众开发
- scala implicit关键字详解(隐式转换函数、隐式类、隐式参数、隐式值)
- 【数位DP】C~K的幸运数字
- msfvenom生成shellcode
- Python: sorted,operator.itemgetter的用法
- Scala知识点整理
- 【Skynet】Sproto初学与应用
- [简单逻辑学]逻辑学的基本原理——万物终有其根源
- Pairs Forming LCM LightOJ
- HDU 6206 Apple(高精度C++)