这里就不翻译Default Arguments的意义了,因为我觉得很难找到一个合适的词来准确的描述它。
Scala 2.8中提供了Default Arguments这个新的特性,终于可以高兴的说:不用再使用implicit argument来做Default Argument了。
先来看看Scala 2.8中Default Argument的使用:
def f(a: Int, b: String = "defaultString", c: Int = 5)
f(1)
f(1, "otherString")
f(1, c = 10) // 需要用具名参数来指定c
|
例子非常简单,但是能够很好的说明Default Argument是如何使用的。这里需要注意一下,除了末尾的参数,如果想跳过任何中间的参数都需要使用具名参数。
在我们使用这个新的特性的时候,Scala 2.8会为每一个Default Argument生成一个方法,比如:
def f(a: Int = 1, b: String)
// generates a method: def f$default$1 = 1
f(b = "3")
// transformed to: f(b = "3", a = f$default$1)
|
当我们在调用方法时使用了Default Argument特性,Scala 2.8就会将生成的方法作为缺失的参数加入到调用的参数列表中去。
Default Argument可以是任意的表达式。因为参数的有效范围包括所有的子参数列表和方法体,我们甚至可以在子参数中使用前一个参数列表中的参数来作为Default Argument。例如:
def f(a: Int = 0)(b: Int = a + 1) = b // OK
// def f(a: Int = 0, b: Int = a + 1) // 错误,找不到变量a
f(10)() // returns 11 (not 1)
|
这里注意到的是,作为Default Argument的参数不能是同一个参数列表中定义的参数,只能是前面参数列表中定义的参数。
接下来是有关泛型的Default Argument的使用。当我们使用 "x:T = expr" 这样的参数时,Scala的类型检查会去寻找最匹配的类型来赋予T:
def f[T](a: T = 1) = a
f() // returns 1: Int
f("s") // returns "s": String
def g[T](a: T = 1, b: T = "2") = b
g(a = "1") // OK, returns "2": String
g(b = 2) // OK, returns 2: Int
g() // OK, returns "2": Any
// g[Int]() // "error: type mismatch; found: String, required: Int"
class A[T](a: T = "defaultString")
new A() // creates an instance of A[String]
|
从上面可以看出,不管是普通的方法还是构造函数,Scala都会为Default Argument来寻找最合适的类型。
Default Argument和其它特性集成
- By-Name Parameters
Default Argument和By-Name Parameters没有任何的冲突。不过在使用时需要知道的是,如果我们在调用方法时忽略了By-Name的参数,那么Scala会在每次调用这个方法时执行一次By-Name参数的Default Argument。
- Repeated Parameters
如果你使用了Repeated Parameters,那么很不幸,Scala不允许你为Repeated Parameters指定任何的Default Argument。
- Overriding
但我们在子类中覆盖或者实现超类的方法时,Default Argument也会被继承或者覆盖掉,例如:
trait A { def f(a: Int = 1, b: Int): (Int, Int) }
// B: inherit & add a defaultclass B extends A { def f(a: Int, b: Int = 2) = (a, b) }
// C: override a defaultclass C extends A { def f(a: Int = 3, b: Int ) = (a, b) }
val a1 = new Bval a2 = new C
a1.f() // return (1,2)
a2.f(b=2) // return (3, 2)
可以看到class B从trait A中实现了f方法,并且对b参数增加了Default Argument,虽然参数a并没有指定Default Argument,但是它可以从trait A中继承;而class C实现了f方法且重写了参数a的Default Argument。
另外,在类型检查时会使用静态类型来检测一个方法的参数是否含有Default Argument,所以下面的用法是不行的:
val a1: A = new B// a1.f() // "error: unspecified parameter: value b"
- Overloading
关于重载方法还是遵循一个原则,就是如果存在多个重载的方法,最终会选择最匹配的那个。那么如果有下面的情况会怎么样呢?
def f(a: Object) // #1def f(a: String, b: Int = 1) // #2
f("str")
在调用f("str")的时候,我们会发现#1和#2两个方法都合适,此时按照原则来说,#1显然最合适,所以此时#1方法会被执行。
- Case Classes
为什么会提及Case Class呢?因为现在Scala 2.8会为每一个case class生成一个名为copy的方法。这个方法的作用顾名思义,就是用来得到这个class的副本。这个方法的参数来自于class的主构造函数,并且它们的Default Argument就是主构造函数的参数。也就是说,如果现在你的case class或者是它的超类中没有定义过copy方法的话,那么Scala就会生成一个。如:
case class A[T](a: T, b: Int) {// def copy[T’](a’: T’ = a, b’: Int = b): A[T’] =// new A[T’](a’, b’)}val a1: A[Int] = A(1, 2)val a2: A[String] = a1.copy(a = "someString")
如果一个case class继承于另外一个case class的话,很明显,只有父类中会存在copy方法,子类只会继承而不会重新生成它。
- Implicit Parameters
額。。。我承认原先是使用Implicit Parameters来实现Default Argument的,但是现在这两个特性可以结合使用了。当我们忘记了指定Implicit Values的时候,这些Default Argument会起作用的。
def f(implicit a: String = "value", y: Int = 0) = a +": "+ yprintln(f) // prints "value: 0"
implicit val s = "size"println(f) // prints "size: 0"