隐式函数

在本教程中,我们将学习如何创建隐式函数。通过使用隐式函数,我们可以为几乎任何类型或类提供扩展方法或函数。 顾名思义,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, "农夫果园")}""")

《PySpark原理深入与编程实战》