隐式转换和隐式参数是
Scala的两个功能强大的工具。隐式转换可以丰富现有类的功能,隐式对象是如何被自动呼出以用于执行转换或其他任务的。利用这两点,我们可以提供优雅的类库。
本文将通过几个示例代码来整体学习一下 Scala 隐式转换的四个特性和运用。它们分别是 隐式函数运用、隐式类扩展运用、隐式参数、类型类(Type class)运用。
隐式转换
implicit conversion function:指的是那种以
implicit关键字声明的带有单个参数的函数。这样的函数将被自动应用,将值从一种类型转换为另一种类型。
隐式函数 Implicit Function
- 定义隐式函数,提供将
LocalDate根据指定format转换为String的功能1
2
3
4
5
6package object implict {
private val formatterNormal: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
implicit def dayIntFormatter(date: LocalDate): String = date.format(formatterNormal)
} - 因为上面的隐式函数定义在 package object 中,如果在同包下的测试类,则不需要 import 上面的隐式函数,直接使用即可。
1
2
3
4
5
6
7
8
9def main(args: Array[String]): Unit = {
val localDate = LocalDate.now()
//自动将 localDate 类型的时间转为 String 类型,并调用函数成功
val data = getDataByStringdate(localDate)
}
def getDataByStringdate(date: String): String = {
"Test Data"
}
隐式类-对目标对象进行功能扩展
定义一个 case class 类 Multiply,并定义一个方法 multiply ,接收一个当前对象,并将值相乘后返回。
定义隐式转换函数 int2Multiply
1 | case class Multiply(m: Int, n: Int) { |
测试类 MultiplyMain
1 | object MultiplyMain { |
- 运行程序结果如下:如果我们提供多个隐式转换函数
1
2
3
4
5结果为:Multiply(6,2)
//计算过程,3 隐式转换为 Multiply(3, 2)
3 => Multiply(3, 2)
//调用multiply计算
Multiply(3, 2).multiply(Multiply(2, 1)) =Multiply( 3*2 , 2*1 )在1
2
3
4
5object MultiplyImplicit {
implicit def int2Multiply(n: Int): Multiply = Multiply(n, 2)
//提供第二个隐式转换函数
implicit def int2Multiply2(n: Int): Multiply = Multiply(n, 3)
}Main中,我们可以通过两种方式进行指定具体使用哪个隐式转换函数。
比如我们选择使用int2Multiply的隐式转换1
2
3
4
5
6
7
8
9
10
11object MultiplyMain {
//方法1: 排除 int2Multiply2 方法,引入其余所有的方法
import com.maple.implic.one.MultiplyImplicit.{int2Multiply2 ⇒ _, _}
// 方法2: 精确引入
import com.maple.implic.one.MultiplyImplicit.int2Multiply
def main(args: Array[String]): Unit = {
val x: Multiply = 3.multiply(Multiply(2, 1))
println(x.toString)
}
}
隐式类,丰富现有类库功能
你是否希望某个类拥有新的方法,而此方法该类并没有提供,那么隐式转换可以丰富这个类,给它提供更多的方法
例如数据库连接类 Connection, 我们希望给它新增一个 executeUpdate 方法来对数据进行修改,例子如下:
1 | package com.maple.implic.two |
测试程序
1 | object ConnectionMain { |
上面通过定义一个 RichConnection 我们可以增强现有类 Connection 的功能。这样一来,通过 connection 对数据库进行增删改查,可以简化大量代码。
隐式参数
函数或方法可以带有一个标记为
implicit的参数列表。在这种情况下,编译器将会查找默认值,提供给本次函数调用。
利用隐式参数进行隐式转换
隐式的函数参数也可以被用作隐式转换。如果我们定义一个泛型函数
1 | def larger[T](x: T, y: T) = if (x > y) x else y |
关于隐式参数的另一件事是,它们可能最常用于提供有关在早期参数列表中显式提到的类型的信息,类似于Haskell的类型类。作为示例,请考虑清单21.2中所示的maxListUpBound函数,该函数返回传递列表的最大元素
1 | def maxListUpBound[T <: Ordered[T]](elements: List[T]): T = { |
maxListUpBound 表示传入一个 List,然后返回 list 中最大的一个元素。功能简单,但是用法十分限制,该List中的成员必须是 Ordered[T] 的子类,否则就会报错。比如我们运行如下例子
1 | object ImplicitParameterMain { |
当我们运行 main 函数时,编译器会报如下错。意思是 Int 不是 Ordered[T] 子类,因此无法使用。
1 | Error:(49, 18) inferred type arguments [Int] do not conform to method maxListUpBound's type parameter bounds [T <: Ordered[T]] |
使用隐式参数优化
如果让
maxListUpBound更通用,我们需要分离这个函数,增加一个参数,来将T转换为Ordered[T],使用隐式参数implicit orders: T ⇒ Ordered[T]来做到这一点。
1 | def maxListUpBound2[T](elements: List[T])(implicit orders: T ⇒ Ordered[T]): T = { |
测试程序:
1 | import com.maple.implic.three.ImplicitParameter._ |
结果为3,并没有报错。这其中编译器是将 Int 转换为了 Ordered[Int]
类型类(type class)
让某个类拥有某个算法,我们无需修改这个类,提供一个隐式转换即可。这种做法相对于面向对象的继承扩展来的更灵活。
看下面两个例子,Ordering 是 Scala 提供的类型类
1 | object Implicits { |
使用,上面两种写法都可以达到相同的功能。
1 | import com.maple.implic.Implicits._ |
Ordering 这样的特质(trait) 被称为类型类(type class,源自 Haskell) 。类型类定义了某种行为,任何类型都可以通过提供相应的行为来加入这个类。这个类是因为共用的目的而组合在一起的类型。
自定义类型类
Scala标准类库提供了不少类型类。比如Equiv、Numeric、Fractional、Hashing、IsTraverableOne、IsTraverableLike等。我们通过自定义一个类型CustomOperation来更深入的学习。
定义特质 CustomOperation
1 | trait CustomOperation[T] { |
在伴生对象中给 String 类型扩展基于 CustomOperation 的功能。
1 | object CustomOperation { |
测试类 CustomOperationMain:
1 | import com.maple.implic.typeclass.CustomOperation._ |
结果如下:
1 | maple<maple> |
如果想要对 Double 支持上述操作,同样定义如下类型类扩展即可:
1 | implicit object DoubleCustomOperation extends CustomOperation[Double] { |
测试用例:
1 | import com.maple.implic.typeclass.CustomOperation._ |
结果为 11.0+16.5
计算过程:
1 | custom.multiply(v, x) + "+" + custom.multiply(v, y).toString |
Type Class 总结
TypeClass 将 行为定义 与 具有行为的对象 分离。有点类似于 AOP,但是比 AOP 简洁很多。同时, 在函数式编程中,通常将 数据 与 行为 相分离,甚至是数据与行为按需绑定,已达到更为高级的组合特性。
隐式转换触发时机
Scala 会考虑如下的隐式转换函数:
- 1.位于源或目标类型的伴生对象中的隐式函数或隐式类。
- 2.位于当前作用域中可以以单个标识符指代的隐式函数或隐式类。
隐式转换可以显示加上,来进行代码调试。
总结
本文主要介绍 Scala 隐式转换的几种用法,通过详细的例子加深读者对隐式转换的理解。关于隐式转换的触发时机以及编译器优化顺序等,将不在本篇文章详细介绍,可以关注笔者后续文章。
推荐
最后推荐一下本人微信公众号,欢迎大家关注。

原文链接: https://www.317318.xyz/p/59e1faba.html
版权声明: 转载请注明出处.
