by 壮衣
在上一篇博文《Scala对JDBC的一些封装》中使用了一个黑魔法: 对java.sql.ResultSet类进行扩展为其添加了rows方法。我们称这种黑魔法为类的动态扩展,可以为已有的类添加新的方法。也许我们觉得给类添加新的方法,可以找到定义类的地方添加新的方法然后重新编译就好了,但是假如我们需要扩展类是Int、String或者JDK中自带类型或者第三方jar包中的类型我们便不好用这个方式扩展了。在Scala中可以通过隐式类来完成类的动态扩展,让我们来看一个简单的例子:我们想给String类添加一个sayHello方法,实现如下代码的效果:
def sayHello(str: String): Unit = println(s"$str Hello")
回顾上一篇博文就知道我们可以定义一个隐式类,在隐式类中实现sayHello方法,然后在需要使用sayHello方法的地方引入隐式类,代码如下:
object StringUtils {
implicit class StringOp(str: String) {
def sayHello(): Unit = println(s"$str Hello")
}
}
如上代码我们在隐式类StringOp中实现了sayHello方法,看看如何使用sayHello方法吧:
import StringUtils._
"zdx".sayHello()
在控制台测试下:
image.png
现在String类型可以调用sayHello函数了,假如我们想给Int类型也添加一个sayHello函数呢?这么看来我们得重新定义一个IntOp的隐式类并在隐式类中实现sayHello方法,还有别的方法吗?能否有一个sayHello方法对任何类型都适用?先试一下方法重载的方案:
def sayHello(s: String): Unit = println(s"$s Hello")
def sayHello(i: Int): Unit = println(s"Hello $i")
嗯,这个方案是ok得。但是能不能把两个sayHello变成一个呢? 再试一下模式匹配:
def sayHello(a: Any): Unit = a match {
case s: String => println(s"$s Hello")
case i: Int => println(s"Hello $i")
case _ => println(s"not support")
}
方法变成一个了,但是假如我们需要新增类型就得重新更改sayHello方法,还有更好的方案吗?现在可以试一下类型类了:
trait Hello[A] {
def hello(a: A): Unit
}
我们定义了一个Hello类型并在其中定义了一个hello方法,先不管hello方法的实现。我们来看一下如何定义一个对于任何类型都适用的sayHello的方法:
def sayHello[A](a: A)(implicit s: Hello[A]): Unit = s.hello(a)
新的定义的sayHello方法可以接受任何类型入参a,也就是说sayHello方法适用任何类型。方法的具体实现其实是通过Hello[A]类型实现的,sayHello方法有一个隐式参数s是Hello[A],通过s的hello方法来实现sayHello方法。那么现在的问题就是我们需要实现Hello[A]类型,先看下Hello[String]类型和Hello[Int]类型如何实现的:
object Hello {
implicit val stringHello: Hello[String] = new Hello[String] {
override def hello(a: String): Unit = println(s"$a Hello")
}
implicit val intHello: Hello[Int] = new Hello[Int] {
override def hello(a: Int): Unit = println(s"Hello $a")
}
}
好了, 现在我们来调用下sayHello方法:
import Hello._
sayHello("zdx")
sayHello(1)
在控制台测试下:
image.png
我们想让sayHello适用新的类型List[Int],使用类型类就不重新必修改sayHello方法了,只需要实现Hello[List[Int]]类型就好了:
implicit val intListHello: Hello[List[Int]] = listSayHello(intHello)
def listSayHello[A](s: Hello[A]) = new Hello[List[A]] {
override def hello(a: List[A]): Unit = a.map(s.hello)
}
对List[Int]使用sayHello函数:
import Hello._
sayHello(List(1, 2, 3))
到现在sayHello函数满足我们的要求,不过我们最开始想的是任何类型都可以调用sayHello方法,而不是写个sayHello方法施用到任何类型,这时候需要通过方法注入来实现:
trait HelloOp[A] {
val h: Hello[A]
val a: A
def sayHello(): Unit = h.hello(a)
}
object HelloOp {
implicit def toHelloOp[A](a1: A)(implicit h1: Hello[A]): HelloOp[A] = new HelloOp[A] {
override val a: A = a1
override val h: Hello[A] = h1
}
}
现在通过toHelloOp这个隐式方法我们可以将任何类型A都转换成HelloOp[A]类型,然后调用sayHello方法。看下如何使用HelloOp :
import HelloOp._
"zdx".sayHello()
1.sayHello()
List(1, 2, 3).sayHello()
同样在控制台测试一下:
image.png
看完这些例子可能觉得类型类的技巧好像很花哨但是却不是很实用,其实类型类type class是从Haskell中传来的概念,当然Scala社区也有ScalaZ和Cats等库提供了丰富的TypeClass,通过这些库可以方便的编写我们得程序,当然这就是一个很大的课题了,留作以后再说明。
网友评论