隐式转换是 Scala 的一项强大功能,允许用户提供一个类型的参数,就像它是另一个类型一样,以避免样板。
请注意,在 Scala 2 中,隐式转换还用于向封闭类提供其他成员(请参见 隐式类)。在 Scala 3 中,我们建议通过定义 扩展方法来解决此用例,而不是隐式转换(尽管出于历史原因,标准库仍然依赖于隐式转换)。
示例
例如,考虑一个方法 findUserById
,它采用一个类型为 Long
的参数
def findUserById(id: Long): Option[User]
为了简洁起见,我们省略了类型 User
的定义,它对我们的示例并不重要。
在 Scala 中,可以调用方法 findUserById
,其参数类型为 Int
,而不是预期的类型 Long
,这是因为该参数将隐式转换为类型 Long
val id: Int = 42
findUserById(id) // OK
此代码不会因“类型不匹配:预期 Long
,但找到 Int
”之类的错误而编译失败,这是因为有一个隐式转换,将参数 id
转换为类型 Long
的值。
详细说明
本节介绍如何定义和使用隐式转换。
定义隐式转换
在 Scala 2 中,从类型 S
到类型 T
的隐式转换由 隐式类 T
定义,该类采用一个类型为 S
的构造函数参数,一个函数类型 S => T
的 隐式值,或一个可转换为该类型值的隐式方法。
例如,以下代码定义了从 Int
到 Long
的隐式转换
import scala.language.implicitConversions
implicit def int2long(x: Int): Long = x.toLong
这是一个可转换为类型 Int => Long
的值隐式方法。
请参阅下面的“小心隐式转换的力量”一节,了解开头部分的子句 import scala.language.implicitConversions
的说明。
在 Scala 3 中,从类型 S
到类型 T
的隐式转换由类型 scala.Conversion[S, T]
的 given
实例 定义。为了与 Scala 2 兼容,也可以通过隐式方法来定义(在 Scala 2 选项卡中了解更多信息)。
例如,此代码定义了从 Int
到 Long
的隐式转换
given int2long: Conversion[Int, Long] with
def apply(x: Int): Long = x.toLong
与其他 given 定义一样,隐式转换可以是匿名的
given Conversion[Int, Long] with
def apply(x: Int): Long = x.toLong
使用别名,可以更简洁地表示为
given Conversion[Int, Long] = (x: Int) => x.toLong
使用隐式转换
隐式转换在两种情况下应用
- 如果表达式
e
的类型为S
,并且S
不符合表达式的预期类型T
。 - 在选择
e.m
中,其中e
的类型为S
,如果选择器m
不表示S
的成员(以支持 Scala-2 风格的 扩展方法)。
在第一种情况下,将搜索适用于 e
且其结果类型符合 T
的转换 c
。
在上面的示例中,当我们将类型为 Int
的参数 id
传递给方法 findUserById
时,将插入隐式转换 int2long(id)
。
在第二种情况下,将搜索适用于 e
且其结果包含名为 m
的成员的转换 c
。
一个示例是比较两个字符串 "foo" < "bar"
。在这种情况下,String
没有成员 <
,因此将插入隐式转换 Predef.augmentString("foo") < "bar"
。(scala.Predef
会自动导入到所有 Scala 程序中。)
如何将隐式转换引入范围?
当编译器搜索适用的转换时
- 首先,它会查看当前词法范围
- 在当前范围或外部范围内定义的隐式转换
- 导入的隐式转换
- 通过通配符导入导入的隐式转换(仅限 Scala 2)
- 然后,它会查看与参数类型
S
或预期类型T
关联的 伴生对象。与类型X
关联的伴生对象是- 伴生对象
X
本身 - 与
X
的任何继承类型关联的伴生对象 - 与
X
中的任何类型参数关联的伴生对象 - 如果
X
是内部类,则为其嵌入其中的外部对象
- 伴生对象
例如,考虑在对象 Conversions
中定义的隐式转换 fromStringToUser
import scala.language.implicitConversions
object Conversions {
implicit def fromStringToUser(name: String): User = (name: String) => User(name)
}
object Conversions:
given fromStringToUser: Conversion[String, User] = (name: String) => User(name)
以下导入将等效地将转换引入范围
import Conversions.fromStringToUser
// or
import Conversions._
import Conversions.fromStringToUser
// or
import Conversions.given
// or
import Conversions.{given Conversion[String, User]}
请注意,在 Scala 3 中,通配符导入(即 import Conversions.*
)不会导入给定定义。
在介绍性示例中,从 Int
到 Long
的转换不需要导入,因为它是在对象 Int
中定义的,该对象是类型 Int
的伴生对象。
进一步阅读:Scala 在哪里查找隐式对象?(在 Stackoverflow 上)。
小心隐式转换的力量
由于隐式转换在不加选择地使用时可能存在缺陷,因此编译器在编译隐式转换定义时会发出警告。
要关闭警告,请执行以下任一操作
- 将
scala.language.implicitConversions
导入隐式转换定义的范围 - 使用
-language:implicitConversions
调用编译器
当编译器应用转换时,不会发出任何警告。
由于隐式转换在不加选择地使用时可能存在缺陷,因此编译器在两种情况下发出警告
- 编译 Scala 2 样式的隐式转换定义时。
- 在将
scala.Conversion
的给定实例作为转换插入的调用位置。
要关闭警告,请执行以下任一操作
- 将
scala.language.implicitConversions
导入以下范围- Scala 2 样式的隐式转换定义
- 将
scala.Conversion
的给定实例作为转换插入的调用位置。
- 使用
-language:implicitConversions
调用编译器