遮蔽(Shadow)與重新定義(Override)


所謂遮蔽(Shadow),是指在某變數可視範圍內,定義了同名變數,在後者的可視範圍中,取用同一名稱時所用的是後者的定義。例如:
val x = 10

{
val x = 20
println(x) // 顯示 20
}

println(x) // 顯示 10

Scala支援區塊可視範圍,在上例中,{}區塊外宣告了一個x變數,而區塊內也宣告了一個x變數,在區塊中x的變數定義遮蔽了區塊外的x定義。

如果你熟悉Java,你會知道這樣的情況在Java中是允許的:
class A {
    protected int x = 10;
}

class B extends A {
    public int x = 20;   // 這邊 x 遮蔽了 A 類別中的 x
}

public class Main {
    public static void main(String[] args) {
        B b = new B();
        A a = b;
        System.out.println(b.x);    // 顯示 20
        System.out.println(a.x);    // 顯示 10
    }
}

不過有的Java開發人員會誤以為在B中重新定義了x為公開(事實上是遮蔽),而對於最後的顯示結果感到錯愕。在Scala中,繼承時幾乎是不允許遮蔽的,例如,以下的範例會編譯失敗:
class Parent {
    protected val x = 10
}

class Child extends Parent {
    val x = 20
}

這樣的情況下,編譯器會認為,你試圖定義一個父類別中已有的成員(也許你不知道這個事實),如果你要這麼作,編譯器會要求你使用override關鍵字,表明你是要重新定義該變數:
error: error overriding value x in class Parent of type Int; value x needs `override' modifier
    val x = 20
         ^

以下的程式才可以通過編譯,在Child中,x被重新定義為公開而值設定為20:
class Parent {
protected val x = 10
}

class Child extends Parent {
override val x = 20
}

val c = new Child
println(c.x) // 顯示為 20

在不可視範圍中,沒有遮蔽問題,就不需要使用override關鍵字,例如:
class Parent {
private val x = 10
}

class Child extends Parent {
val x = 20 // 因為 Parent 的 x 在這邊不可視,所以不用 override
}

val c = new Child
println(c.x)

父類別中的成員,在子類別中重新定義時,權限不能縮減,只能重新定義為更寬的權限。例如以下通不過編譯:
class Parent {
    val x = 10
}

class Child extends Parent {
    override protected val x = 20  // error, value x has weaker access privileges
}

不過這有個例外,就是private[this],例如以下可以通過編譯:
class Parent {
val x = 10
}

class Child extends Parent {
private[this] val x = 20
def getX = x
}

val c = new Child
println(c.x) // 顯示 10
println(c.getX) // 顯示 20

重 新定義成員時,主要是為了避免影響已使用你程式的客戶端。然而宣告為private[this]的成員,是完全只能在類別中使用,不可能透過參考名稱來存 取該成員,也就是客戶端完全不可能使用到你宣告為private[this]的成員,所以這種情況是允許的,而在這種情況下,例如上例中,Child中的 x在類別中,遮蔽了Parent的x,因此透過getX取回時會是20的值。

另外要注意的是,你不可以重新定義var為val,例如下例通不過編譯:
class Parent {
    var x = 10
}

class Child extends Parent {
    override val x = 10    // error, value x cannot override a mutable variable
}

這可以理解,如果x在父類別中是可變動的,若父類別中某些演算方法會改變x的值,在繼承後你將之設為val,原本那些演算方法該怎麼辦呢?