隐式(implicit)是scala中最有特色也是最困难的一部分,可以说不掌握隐式就不能算是真正掌握了scala。隐式在许多著名的scala项目中有着广泛的应用,如Spark、GeoMesa等。本文就来详细介绍一下scala中隐式的使用。
scala中的隐式可以分为三部分内容:
- 隐式参数
- 隐式转换类型
- 隐式调用函数
- 隐式类 (scala2.10+)
1. 隐式参数
如果scala中的方法的参数使用implicit关键字标识,那么即使在调用方法并没有指定该参数,scala会寻找一个具有跟参数类型相同的变量,并将此变量作为参数传递给该方法,看一个实例:
abstract class Monoid[A] {
def add(x: A, y: A): A
def unit: A
}
object ImplicitTest {
implicit val stringMonoid: Monoid[String] = new Monoid[String] {
def add(x: String, y: String): String = x concat y
def unit: String = ""
}
implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
def add(x: Int, y: Int): Int = x + y
def unit: Int = 0
}
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
if (xs.isEmpty) m.unit
else m.add(xs.head, sum(xs.tail))
def main(args: Array[String]): Unit = {
println(sum(List(1, 2, 3))) // uses IntMonoid implicitly
println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
}
}
上例中stringMonoid方法与intMonoid方法在前面加了implicit关键字,意思是该方法是隐式方法。sum方法还有多个参数列表,通过scala科里化方法可以只指定第一个参数xs,那么第二个参数m在不指定的情况下,scala仍然可以根据传入的xs:List[A]中A的具体类型推断出使用stringMonoid方法或者intMonoid方法來赋值给m。这就是隐式参数达到的效果。
这里要注意一个很重要的规则:
Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called.Scala在调用包含有隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数(无前缀)。
2. 隐式转换
使用隐含转换将变量转换成预期的类型是编译器最先使用 implicit 的地方。这个规则非常简单,当编译器看到类型X而却需要类型Y,它就在当前作用域查找是否定义了从类型X到类型Y的隐式定义。例子:
class Person(val name: String) {
def talk(person: Person) {
println(this.name + " speak to " + person.name)
}
}
class Dog(val name: String)
这里定义了两个类:Person和Dog,Person具有一个talk方法,要求参数为Person类型,如过不使用隐式,下面代码会报错:
var dog = new Dog("wangcai");
var man = new Person("zhangsan");
man.talk(dog);
如果使用如下语句,可正常运行:
object convert {
implicit def dog2person(d: Dog): Person = new Person(d.name)
}
import scala.tutorial.convert._
var dog = new Dog("wangcai");
var man = new Person("zhangsan");
man.talk(dog);
因为Dog并不匹配Person类型,scala会自动寻找标记了implicit的Dog=>Person类型的方法,然后进行转换。
3. 隐式调用函数
隐式调用函数可以转换调用方法的对象,比如但编译器看到X .method,而类型 X 没有定义 method(包括基类)方法,那么编译器就查找作用域内定义的从 X 到其它对象的类型转换,比如 Y,而类型Y定义了 method 方法,编译器就首先使用隐含类型转换把 X 转换成 Y,然后调用 Y 的 method。
例子:
class SwingType {
def wantLearned(sw: String) = println("兔子已经学会了" + sw)
}
object swimming {
implicit def learningType(s: ImplicitTest) = new SwingType
}
object ImplicitTest {
import scala.tutorial.swimming._
...
val rabbit = new ImplicitTest
rabbit.wantLearned("蛙泳") //蛙泳
}
输出结果:兔子已经学会了蛙泳
rabbit为ImplicitTest类型,并没有wantLearned方法,但是swimming中有一个Implicit=>SwingType的隐式,而SwingType有wantlearned方法,所以上面程序还是可以正常运行。
4. 隐式类
scala2.10后提供了隐式类,可以使用implicit声明类型,具体要求如下:
- 1.其所带的构造参数有且只能有一个
- 2.隐式类必须被定义在类,伴生对象和包对象里
- 3.隐式类不能是case class(case class在定义会自动生成伴生对象与2矛盾)
- 4.作用域内不能有与之相同名称的标示符
object Stringutils {
implicit class StringImprovement(val s : String){ //隐式类
def increment = s.map(x => (x +1).toChar)
}
}
object Main extends App{
import com.mobin.scala.implicitPackage.Stringutils._
println("mobin".increment)
}
编译器在mobin对象调用increment时发现对象上并没有increment方法,此时编译器就会在作用域范围内搜索隐式实体,发现有符合的隐式类可以用来转换成带有increment方法的StringImprovement类,最终调用increment方法。
原链接 :https://blog.csdn.net/xiaof22a/article/details/80476646