本文链接: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
的语法格式其实只是语法糖,但是如此简单就可以使用工厂模式,何乐而不为呢?更何况还可以有模式匹配的支持。