隐式参数(IMPLICIT PARAMETERS)
方法可以有一个隐式参数列表,在参数列表的开头用 implicit
关键字标记。如果参数列表中的参数没有像往常那样传递,Scala 将会查看它是否可以获取一个正确类型的隐式值,如果可以,则自动的传递它。
Scala 将寻找参数的位置分为两类:
- 在带有隐式参数块的方法被调用时,Scala 首先会查找可以被直接获取(没有前缀)的隐式定义和隐式参数。
- 然后它会查看与隐式候选类型相关联的全部伴生对象(companion objects)中的隐式标记成员。
可以在 FAQ 找到有关 Scala 是如何寻找隐式位置的更详细指南。
在下面的例子中,我们定义了一个方法 sum
,它使用 monoid 的add
和 unit
计算元素列表的总和。请注意隐式值不能是顶级的(top-level)。
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
}
}
Nonoid
定义了一个 add
操作,它组合了一对 A
并返回另一个 A
,以及一个称为 unit
的操作能够创建一些特定的 A
。
为了演示隐式参数是如何工作的,我们首先分别为字符串和整数定义了 monoid StringMonoid
和 IntMonoid
。
关键字 implicit
指明相应的对象可以被隐式地使用。
方法 sum
接受 List[A]
并返回 A
,它将从 unit
中取出初始值 A
,并将列表中每下一个 A
与方法 add
结合起来 。使参数 m
隐式在这里指在我们在调用方法时只需提供 xs
参数,如果 Scala 可以找到隐式 Monoid[A]
用于隐式参数 m
的话。
在我们的 main
方法中,我们调用两次 sum
方法,并只提供了 xs
参数。现在 Scala 将会在上面提到的范围内查找隐式。第一次调用 sum
方法时传递了一个 List[Int]
给 xs
,这意味着 A
是 Int
类型。隐式参数列表 m
被忽略了,因此 Scala 将会查找隐式类型 Monoid[Int]
。按照第一查找规则读取:
在带有隐式参数块的方法被调用时,Scala 首先会查找可以被直接获取(没有前缀)的隐式定义和隐式参数。
intMonoid
是一个可以在 main
函数中被直接获取的隐式定义。它也是正确的类型,所以它会自动地传递给 sum
方法。
第二次调用 sum
时 传入了一个 List[String]
,这意味着 A
是 String
类型。隐式查找的方式与 Int
相同,但是这次将会找到 stringMonoid
方法并自动地将其传入为 m
。
该程序输出结果为:
6
abc
隐式转换(IMPLICIT CONVERSIONS)
一个 S
类型到 T
类型的隐式转换由具有函数类型 S => T
隐式值或由可转化为该类型的隐式方法定义。
隐式转换适用于两种场景:
- 如果表达式
e
是类型S
,并且S
不符合表达式的预期类型T
。 - 在选择带有
S
类型e
的e.m
中,如果选择器m
不表示成员S
。
在第一种情况下,搜索一个适用于 e
的转换 c
并且其结果要符合类型 T
。第二种情况,搜索一个适用于 e
的转换 c
并且其结果类型要包含名为 m
的成员。
如果一个隐式方法 List[A] => Ordered[List[A]]
在范围内,隐式方法 Int => Ordered[Int]
也在,那接下来对两个类型列表 List[Int]
的操作是合法的:
List(1,2,3) <= List(4,5)
通过 scala.Predef.intWrapper
,隐式方法 Int => Ordered[Int]
自动地被提供。下面提供一个隐式方法 List[A] => Ordered[List[A]]
实现的例子。
import scala.language.implicitConversions
implicit def list2ordered[A](x: List[A])
(implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] =
new Ordered[List[A]] {
//replace with a more useful implementation
def compare(that: List[A]): Int = 1
}
隐式导入的对象 scala.Predef
声明了几个预定义类型(如 Pair
)和方法(如 assert
),而且也包含几个隐式转换。
例如,当调用一个 Java 方法期望获得一个 java.lang.Integer
时,你可以使用 scala.Int
代替。这是因为预定义包括了接下来的隐式转换:
import scala.language.implicitConversions
implicit def int2Integer(x: Int) =
java.lang.Integer.valueOf(x)
因为隐式转换有缺陷,如果不加以区分的使用,编译器将会在编译隐式转换定义时发出警告。
可以采取下面一些方法来关闭警告:
- 导入
scala.language.implicitConversions
到隐式转换定义的范围 - 调用编译器时带参数
-language:implicitConversions
这样,当编译器在进行转换时,就不会再发出警告。