天天看點

關于靜态方法為什麼不能被重寫的一點思考以及overload的一些坑。

重寫”隻能适用于可觀察的執行個體方法.不能用于靜态方法和final、private.對于靜态方法,隻能隐藏。一方面這是Java的規定,另一方面其實也有一定的道道這裡邊。

首先談談java方法是何時綁定的

我們大家平時使用一個對象的方法時可能是這樣的

Shape shape = new Rectangle();

shape.getArea();

那麼請問大家知道getArea是該調用父類Shape 的方法呢還是該調用Rectangle的方法呢。實際情況是:在運作期的時候取決于是哪個對象調用他的,規則是優先調用自己的這個getArea方法,如果自己沒有這個方法就調用父類的getArea方法(這一切都是基于反射的,大家還記得設計模式裡有一個模版方法模式吧,如果還不明白請往下看)。這裡聲明成Shape 類型變量,是讓我們忘記它的具體實作類型,将做什麼和怎麼做分離,這就是java的多态。當然這些如果運用的好的話,将會非常有效的改善我們代碼的組織結構和可讀性并且提升程式的擴充性。

但是我們每個方法的調用者并不都是由對象來調用他的。比如說static方法,可能有人會覺得這個static方法調用者是類對象啊,但我告訴你類對象是Class類型,也就是每個類的class靜态域,比如String.class、ArrayList.class,他們并不包含我們要調用的目标靜态方法。。。想必大家也碰到過調用類的非靜态方法時的NullPointException吧,那是因為我們的調用者是null變量,它裡面沒有我們的目标方法。現在大家想一想下面這個程式執行完成會是什麼結果(我可以告訴大家,是可以正常運作的)

public class NullInvoker {
    public static void testNullInvoker(){
        System.out.println("in testNullInvoker");
    }
    public static void main(String[] args) {
        NullInvoker NULL = null;//雖然這裡是空指針,但是下面依然可以執行下去
        NULL.testNullInvoker();
    }
}
           

在Java中我們靜态方法的的選擇是編譯期就選擇好的,是編譯器自動根據聲明類型幫我們選擇的,它不依賴與任何對象。下面這個小例子,大家測試一下吧

public class TestStaticMethod {
    public static void main(String[] args) {
        SubClass s = new SubClass();
        Parent p = s;
        p.testStatic();// 列印父類方法。
        s.testStatic();// 列印子類方法。
    }
}
class Parent {
    public static void testStatic() {
        System.out.println("父類方法");
    }
}
class SubClass extends Parent {
    public static void testStatic() {
        System.out.println("子類方法 ");
    }
}
           

是以說從語義就可以看出static、final、private方法本身都是編譯期綁定的(也叫前期綁定)這些方法不存在多态,他們是在還沒有運作的時候,程式在編譯器裡面就知道該調用哪個類的哪個方法了,而其他可觀察的普通方法的綁定是在運作的時候根據具體的對象決定的,因為從語義上看這些方法是可被繼承的,有了多态而造成了不确定性。。。

是以不管是java語言的硬性規定還是java語言的一些關鍵字的語義,大家應該都會明白靜态方法為什麼不能被覆寫了吧。

上面說了這麼多,現在再說說關于重載的一些坑吧

首先我們看看代碼,大家覺得這段代碼會輸出什麼

public class TestOverLoadMethod {

public void testOverLoad(ArrayList<String> arrayList){
    System.out.println("ArrayList");
}
public void testOverLoad(LinkedList<String> linkedList){
    System.out.println("LinkedList");
}
public void testOverLoad(List<String> list){
    System.out.println("list");
}

public static void main(String[] args) {
    List<List<String>> lists = new ArrayList<List<String>>();
    lists.add(new ArrayList<String>());
    lists.add(new LinkedList<String>());
    lists.add(new CopyOnWriteArrayList<String>());
    TestOverLoadMethod obj = new TestOverLoadMethod();
    for (List<String> list : lists) {
        obj.testOverLoad(list);
    }
}
           

}

下面是輸出結果

list

list

list

明明傳的參數是ArrayList和LinkedList,為什麼最後調用的都是testOverLoad(List list)這個方法呢。。。這裡大家注意一下,雖然我們具體方法的選擇是運作時才确定的,但是我們方法簽名的确定卻是編譯期就決定的。因為我們的編譯器看到obj.testOverLoad(list);調用時候list的聲明類型是List,是以這個調用生成的簽名是testOverLoad(List list),而他在運作時根據反射調用這個方法會輸出3個list字元串就不為怪了。

細心的朋友可能會注意到java平台類庫中有些類中比如DataOutputStream有些方法叫

writeInt(int v)

writeLong(long v)

writeFloat(float v)等等許多方法,現在大家應該能猜到是什麼原因吧?

是以兩個方法的簽名如果能夠完全區分時,可以使用overload。如果兩個方法簽名存在一些交集時,請慎用overload了,就像java平台類庫一樣,采用一些語義明确的方法名,那樣代碼會更清晰明了,使用者也不會犯迷糊。。。這裡再補充一下,重載不是多态,重載方法的确定是編譯期間就能确定的,是寫代碼的人手動選擇的,不是運作時動态選擇的。覆寫是多态,重載是例外。

歡迎大家來交流,或贊或噴皆可,說的好的我會感激,說的不好的我也不會介意。