本文链接:Scala学习(七)——Apply和Unapply
简介
apply和unapply指的是持有这两个名字的方法,对于这两个方法,Scala编译器做了一些特殊的设定,赋予了它们特殊的意义,让它们看起来更好看一点。
从数组讲起
在前面的内容里,我们已经定义过数组了,但是还从来没有介绍过如何使用数组,在这里补齐一下:
| 1 2 3 4 5 | val arr = new Array[Int](10)  // 定义Int类型,长度为10的数组,可以看到这里用了泛型 val arr2 = Array(1, 3, 5, 7)  // 直接从内容构造数组,类似Java里的new int[]{1, ...} arr(3) = 10       // 赋值 println(arr2(3))  // 使用 | 
刚接触Scala的人一定很疑惑为什么使用()而不是常用的[]作为数组访问符号,虽然说[]已经被用于泛型。实际上呢,Scala根本就没有(有特殊语法的)数组,有的只是Array类,用小括号访问数组的思路,其实就是使用了apply。
apply
某种意义上讲,apply方法其实是对()的重载,作用是给对象赋予可被调用的特征:
| 1 2 3 4 5 6 7 8 | class Vec3(val x: Int, val y: Int, val z: Int) {   def apply(i: Int): Int = if(i == 0) x else if(i == 1) y else z } val v = new Vec3(1, 2, 3) println(v(0))  // 1 println(v(1))  // 2 println(v(2))  // 3 | 
最后三句等价于:
| 1 2 3 | println(v.apply(0)) println(v.apply(1)) println(v.apply(2)) | 
当然,apply函数可以是任意参数,任意返回值,同时也可以存在多个不同参数的apply函数。
update
update方法是对()=的重载,作用是修改通过apply获得的值:
| 1 2 3 4 5 6 7 8 9 10 11 | class Vec3(var x: Int, var y: Int, var z: Int) {   def apply(i: Int): Int = if(i == 0) x else if(i == 1) y else z   def update(i: Int, v: Int): Unit =     if(i == 0) x = v     else if(i == 1) y = v     else z = v } val v = new Vec3(1, 2, 3) v(1) = 10 println(v(0))  // 10 | 
同样也可以有任意参数,任意返回值,任意重载。
unapply
unapply方法应当是apply方法的逆操作,其输入是apply的返回值时,输出应当是apply的参数。unapply方法的用处在于以前提到过的match语句。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class AClass(val v: Int) object AFactory {   def apply(a: Int): AClass = new AClass(a)   def unapply(a: Any): Option[Int] =     a match {       case e: AClass => Some(e.v)       case _ => None     } } val a = AFactory(5) a match {   case AFactory(x) => println(x)   case _ => println("other") } | 
上面这一例程序中,AFactory对象同时有apply和unapply方法数,其中apply方法可以构造一个AClass类型的对象,unapply方法可以从这个对象中提取出构造时输入的值。正是有了unapply方法,下面的match语句才可以判断a是否是apply方法的输出值,并且获得原始的参数。
有关unapply方法本身,值得注意的地方有两点。一是输入参数为一个,类型为Any,这样才可以保证对所有对象的match语句有效。二是输出类型为Option[T]类型,其中T是原参数的实际类型。Option类型暂不细讲,会在以后说明。这里如果匹配成功,就返回用Some包含的参数,如果匹配失败,则返回None对象。
如果原始apply方法的参数有多个,那么unapply就不够用了,需要使用unapplySeq,意义是相同的。具体用法见下面这个例子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Vec3(val x: Int, val y: Int, val z: Int) object Vec3 {   def apply(x: Int, y: Int, z: Int) = new Vec3(x, y, z)   def unapplySeq(v: Any): Option[Seq[Any]] =     v match {       case v: Vec3 => {         val result = new Array[Int](3)         result(0) = v.x         result(1) = v.y         result(2) = v.z         Some(result)       }       case _ => None     } } val v1 = Vec3(1, 2, 3) v1 match {   case Vec3(x, y) => println(x, y)        // 参数个数不对也不会触发   case Vec3(x, y, z) => println(x, y, z)  // 输出(1,2,3)   case _ => println("other") } | 
当然,unapplySeq的功能是unapply的超集,但是效率可能稍低,所以可以用unapply的地方还是尽量用。
总结
调用apply和unapply的语法格式其实只是语法糖,但是如此简单就可以使用工厂模式,何乐而不为呢?更何况还可以有模式匹配的支持。