這是物件導向程式語言中,類別繼承所造成的問題,當有類別被其他類別繼承時,常會遇到一些 method 被子類別覆寫 override 的狀況,但覆寫 method 後,卻可能會因為不了解父類別實作的細節,導致程式出錯。
以下是實際的例子
Super 提供兩個 method,但 inc1 會在method 裡面呼叫 inc2,Sub 繼承了 Super,但並不知道 inc1 的實作內容,在 Sub 裡面,覆寫了 inc2,將 inc2 改成呼叫 inc1,因此在使用 Sub 的 inc2 時,會一直重複呼叫函數,最後導致發生 java.lang.StackOverflowError
。
class Super {
private int counter = 0;
void inc1() {
inc2();
}
void inc2() {
counter++;
}
}
class Sub extends Super {
@Override
void inc2() {
inc1();
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.inc2();
}
}
$ java Sub
Exception in thread "main" java.lang.StackOverflowError
at Super.inc1(Super.java:6)
at Sub.inc2(Super.java:21)
這跟上面的例子類似,一樣是會發生重複呼叫函數的問題,最後導致發生 java.lang.StackOverflowError
。
class Base{
protected int x;
protected void m(){
x++;
}
protected void n(){
x++; // <- defect
m();
}
}
class SubBase extends Base{
protected void m(){
n();
}
public static void main(String[] args) {
SubBase sub = new SubBase();
sub.m();
}
}
$ java SubBase
Exception in thread "main" java.lang.StackOverflowError
at Base.n(Base.java:9)
at SubBase.m(Base.java:16)
at Base.n(Base.java:9)
Java 提供一種解決方式,就是將無法被覆寫的 method 加上 final。
class Super {
private int counter = 0;
void inc1() {
inc2();
}
final void inc2() {
counter++;
}
}
class Sub extends Super {
@Override
void inc2() {
inc1();
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.inc2();
}
}
在編譯時,就會因為 final 的關係,造成編譯錯誤。
$ javac Super.java
Super.java:20: error: inc2() in Sub cannot override inc2() in Super
void inc2() {
^
overridden method is final
1 error
Kotlin 提供更積極的解決方案,所有 method 在不加上任何 modifier 的狀況下,預設都是有 final 特性的,如果可以讓子類別覆寫的 method,必須要加上 open 這個 modifier。
這樣的調整,可避免 method 任意被覆寫的問題,但相對的,programmer 要承擔更多責任,判斷什麼時候該加上 open
,這有時候會造成一些困擾,就是不知道什麼時候要加上 open,就變成不寫 open。
open class Super {
open fun v() {}
fun nv() {}
}
class Sub() : Super() {
override fun v() {}
}
最後只能說,沒有兩全其美的解決方案,就看程式語言的設計者,認定哪一種想法比較重要。
沒有留言:
張貼留言