隐式函数
在本教程中,我们将学习如何创建隐式函数。通过使用隐式函数,我们可以为几乎任何类型或类提供扩展方法或函数。 顾名思义,Scala从一开始就是可扩展的。隐式的用法,是Scala提供的特性之一,通过它我们可以轻松地向任何类型或类添加扩展方法或函数。
在下面这个示例中,我们将扩展String类,使其具有isFavoriteFruit()函数。
object demo04 { // 创建一个简单的包装类FruitString, // 它将使用String类型作为参数,然后提供一个isFavoriteDonut()函数。 class FruitString(s: String) { def isFavoriteFruit: Boolean = s == "苹果" } // 创建隐式函数转换字符串到包装字符串类 // 将隐式函数或转换封装到一个使用对象的单例中是一个很好的实践 // 义一个名为stringToFruitString的隐式函数,该函数将以String类型作为参数, // 并通过名为FruitString的String包装器类的新实例将其连接起来。 object FruitConverstions { // 定义隐式函数类似于定义任何其他函数,只是使用implicit关键字作为函数签名的前缀。 implicit def stringToFruitString(s: String) = new FruitString(s) } def main(args: Array[String]): Unit = { // 为了使用隐式String函数将String类型转换为FruitString类型, // 必须在作用域内使用隐式函数。这可以使用import关键字实现 import FruitConverstions._ // 创建两个String类型的不可变值,分别代表不同的水果 val apple = "苹果" val gooseberry = "弥猴桃" // 访问 isFavoriteFruit() println(s"苹果是不是最喜欢的水果 = ${apple.isFavoriteFruit}") println(s"弥猴桃是不是最喜欢的水果 = ${gooseberry.isFavoriteFruit}") } }
执行上面的代码,输出内容如下:
苹果是不是最喜欢的水果 = true 弥猴桃是不是最喜欢的水果 = false
Tip:
- 将隐式函数和值封装到Object或Package Object中是一个很好的实践。
- Scala Predef类利用隐式函数提供现成的转换,比如Java从/到Scala的转换。
隐式值
隐式参数的使用是Scala中如何实现依赖注入的一个例子。事实上,依赖注入是内置在Scala语言中的,这样就不必导入另一个第三方库。
在下面的示例中,定义了一个函数,带有一个隐式参数。在执行时,Scala编译器将寻找Double类型的隐式值。如果作用域中没有隐式值,则会得到一个编译器错误。因此,我们将在代码库中某处定义一个Double类型的隐式值。定义隐式值类似于使用val关键字定义任何其他值,只是在val关键字前面加上implicit关键字。
object demo { def main(args: Array[String]): Unit = { // 定义一个Double类型的隐式值 implicit val discount: Double = 0.1 println(s"所有客户将收到一个 ${discount * 100}% 折扣") println(s"""加上5个苹果的折扣价 = ${totalCost("苹果", 5)}""") } // 定义一个带有隐式参数的函数 // 在执行时,Scala编译器将寻找Double类型的隐式值。 // 如果作用域中没有隐式值,则会得到一个编译器错误。 def totalCost(fruitType: String, quantity: Int)(implicit discount: Double): Double = { println(s"计算 $quantity 个 $fruitType 的价格") val totalCost = 2.50 * quantity * (1 - discount) totalCost } }
如何定义一个函数接受多个隐式参数?
定义额外的隐式参数类似于定义任何其他参数,只需使用逗号分隔参数。在下面的例子中,我们扩展totalCost()函数,以接受一个隐含的String类型参数,该参数表示水果商店的名称。
因此,需要首先定义另一个String类型的隐式值,以便它在调用totalCost()函数之前处于作用域中。
object demo { def main(args: Array[String]): Unit = { // 定义一个Double类型的隐式值 implicit val discount: Double = 0.1 println(s"所有客户将收到一个 ${discount * 100}% 折扣") println(s"""加上5个苹果的折扣价 = ${totalCost("苹果", 5)}""") implicit val storeName: String = "农夫果园" println(s"""加上5个苹果的折扣价 = ${totalCost2("苹果", 5)}""") } // 定义一个带有隐式参数的函数 // 在执行时,Scala编译器将寻找Double类型的隐式值。 // 如果作用域中没有隐式值,则会得到一个编译器错误。 def totalCost(fruitType: String, quantity: Int)(implicit discount: Double): Double = { println(s"计算 $quantity 个 $fruitType 的价格") val totalCost = 2.50 * quantity * (1 - discount) totalCost } // 多个隐式参数 def totalCost2(fruitType: String, quantity: Int)(implicit discount: Double, storeName: String): Double = { println(s"[$storeName] 计算 $quantity 个 $fruitType 的价格") val totalCost = 2.50 * quantity * (1 - discount) totalCost } }
如何手动传递隐式参数
在极少数情况下,我们可能不得不手动传递隐式参数值。这可以通过下面所示的另一对括号传递隐式参数来实现。
// 手动传递隐式参数 println(s"""加上5个苹果的折扣价, 手动传参 = ${totalCost2("苹果", 5)(0.1, "农夫果园")}""")