隐式转换是 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调用编译器