http://twitter.github.io/scala_school/zh_cn/basics2.html
- apply方法
apply方法给了类或者对象一个很方便的语法糖,我们在实例化对象时,看起来就像是在使用一个方法,比如:
scala> class Foo {}
defined class Foo
scala> object FooMaker {
| def apply() = new Foo // 伴生类中定义apply
| }
defined module FooMaker
scala> val newFoo = FooMaker() // 不需要new了,直接使用半生对象中的方法就能实例化
newFoo: Foo = Foo@5b83f762
或者这样:
scala> class Bar {
| def apply() = 0 // 类中定义apply
| }
defined class Bar
scala> val bar = new Bar
bar: Bar = Bar@47711479
scala> bar() // 可以直接使用,而不需要bar.apply
res8: Int = 0
apply是一种很方便的语法糖。
- 单例对象
在刚才的例子中,注意到Foo和FooMaker是不一样的名字,因此,如果希望直接用:
Foo()
这是不可能的,只能用FooMaker()。那如果希望更方便的话,将单例对象直接命名为类的名字就可以了。此时,单例对象也叫做该类的伴生对象。
class Foo {}
object Foo{
def apply()=new Foo
}
Foo()
伴生对象
- 函数即对象
这里的extend表明了继承关系,继承了一个叫做Fuction1[Int, Int]的东西。
scala> object addOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined object addOne
scala> addOne(1)
res2: Int = 2
这个Function1是一个trait,它有一个很方便的apply方法,如上所述。
那么同样的,还可以这么做:
case class TestFunc1() extends Function1[Int, Int]{
def apply(i: Int): Int = i
}
scala> TestFunc1
res45: TestFunc1.type = TestFunc1
scala> TestFunc1()
res46: TestFunc1 = <function1>
scala> TestFunc1()(11)
res47: Int = 11
这里就要来解释一下到底这是在干什么了。我们还是以数学的方式比较。我们先看一下trait Function1是什么。
trait Function1[-T, +R] extends AnyRef { self =>
def apply(v1: T1): R
...
}
这个特质,表达的是一种单参数函数关系。就如同一个数学函数,
Anything => Anything
表达了一种对应关系,这是一种一元对应关系,比如:
y = x + 1, 一元一次函数;
y = x^2 + x + 1,一元二次函数;
这都是一元对应关系。
同时,因为这种关系并没有对定义域和值域有任何要求,表达的是一种抽象关系,于是我们用trait来表达。同时应该记得,trait也是用来给类增加一些特性
的,比如之前说的,很多动物都能发声
。Function1提供了一个特性是apply()。它的好处是,可以直接使用SomeClass(input)。
这个特性反映在数学上,就是针对这种抽象的对应,如果我们希望将其落实到具体的函数上怎么办?落实,也就是将trait的抽象,反映到具体的类/对象上。
trait有两个部分是可以具体化的。
首先,Function1使用的是泛型,这个类型首先可以具体化。数学表达就是
Int => Int
我们希望得到抽象结构的子集,只考虑Int的情况。这个的解决办法是
//数学上依然抽象的,这样的情况就是没指定类型的Function1;类形上有指定,为泛型
abstract class TestG[K, V] extends Function1[K, V]
//数学上抽象,因为没有定义函数本身
abstract class TestAbs extends Function1[Int, Int]
trait TestAbsT extends Function1[Int, Int]
为什么这样?我们不能使用case class/class,因为Function1还有另外一个抽象的部分,apply方法。只有这个方法具体化了,我们才能将其实例化。反映到数学上,它的表达就是
Int => Int
y = x^2 + 2x + 1
我们希望得到Int函数的子集,一个具体的一元二次函数。
这个很具体了,我们于是可以
case class TestIntFunc() extends TestAbs {
def apply(i: Int): Int = i*i + 2 * i + 1
}
case class TestBi() extends Function1[Int, Int] {
def apply(x: Int): Int = x*x + 2 * x + 1
}
这样,我们就得到了一个定义域/值域为[Int, Int],且具体的一元二次函数。小结一下这些概念的对应。
定义域/值域不固定,抽象函数,Function1
定义域/值域固定,抽象函数,继承自Function1的trait/abstract
定义域/值域固定,具体函数,class/case class
这里还有一个很容易混淆的东西,apply。
我们知道case class是有伴随的object,定义了很方便的apply,让其可以当作CaseClass(item),类似函数一样使用,直接得到某个实例,
而Trait也定义了一个apply。这两个apply有什么区别?联系?
class TestBiClass extends Function1[Int, Int] {
def apply(x: Int): Int = x*x + 2 * x + 1
}
case class的apply,是帮助其实例化从而建立一个新的obj,即省略了new CaseClass这一步的;
Function1的apply,是帮助函数自动运行某种计算的,即省略了NewedClass.apply()而直接使用NewedClass()的。
所以apply这个方法是一个特殊的方法,所有的类都可以建立自己的apply方法,从而省略.apply而直接使用SomeClass()。
class TestApply{
def apply(x: Int): Int = x*x + 2 * x + 1
}
当apply作用于case class时,这种省略是为了节约new这个步骤,直接得到obj。
当apply作用于class,尤其是某种函数形成的class时,这种省略是为了进行某种运算,直接得到结果。
同时应该知道,case class的apply,位于其伴随obj中,是自动生成的。class的apply则位于类的内部。那如果我们给class也加一个伴随obj,并添加apply方法,是不是就可以和case class一样了?当然!见前文的单例对象/伴生对象。
接下来,我们从一个class到instance来看看发生了什么。
class TestBiClass extends Function1[Int, Int] {
def apply(x: Int): Int = x*x + 2 * x + 1
}
//这样是不行的,因为没有伴生对象
scala> TestBiClass
<console>:25: error: not found: value TestBiClass
TestBiClass
//生成了一个TestBiClass实例
scala> val tb = new TestBiClass
//这个实例是一个类型为Function1的函数。如果从对象角度理解,这是一个类型为TestBiClass的实例对象,它有一个方法为apply。
res69: TestBiClass = <function1>
//我们可以使用apply方法,就如所有其他的class的对象一样。实例对象使用类的方法。
scala> tb.apply(2)
res70: Int = 9
//但因为apply方法的特性,我们可以省略掉它,直接使用
scala> tb(2)
res71: Int = 9
//单纯从函数的角度理解,tb是一个函数,函数如何使用?f(x) = x^2+2x+1,f函数构建在定义域x之上。
//那么要使用它,就是f(x)。TestBiClass是一个函数,定义在x这个整数类型之上,要用它就是TestBiClass(x)。
//这就是另外一层含义了。
scala> tb(2)
res71: Int = 9
同样的例子也可以用在case class身上。
case class TestBi() extends Function1[Int, Int] {
def apply(x: Int): Int = x*x + 2 * x + 1
}
//告诉我们TestBi是一个类(型)
scala> TestBi
res72: TestBi.type = TestBi
//case class自带的apply帮我们完成了实例化,我们得到了一个TestBi实例。这个实例是一个Function1[Int, Int]类型的函数
//理论上来说已经精确到Function1[Int, Int]上了。Function1[T, R]算是泛型的trait,这里具体化了。
scala> TestBi()
res73: TestBi = <function1>
//这是一个类型为TestBi的类的实例化对象,因此我们可以使用该类的方法,有继承自Function1的apply方法。
scala> TestBi().apply(2)
res74: Int = 9
//同样,因为apply方法的特性,我们可以省略掉它,直接使用
scala> TestBi()(2)
res75: Int = 9
//这里关于对象和函数的具体解释和上面的例子是一样的。
//如果我们查看一下TestBi()这个实例,我们就知道其真实的父类是什么
scala> TestBi().isInstanceOf[Function1[Int, Int]]
res78: Boolean = true
scala> TestBi().isInstanceOf[TestBi]
res79: Boolean = true
- 异常
这里可以看到,首先try-catch-finally,这个结构是面向表达式的,也就是说,它本身是有一个结果的,在这里是Int。
同时,finally在最后被调用,且不是表达式的一部分,也就是说其结果并不会做为值被传递return。
val result: Int = try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => {
log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
0
}
} finally {
remoteCalculatorService.close()
}