下面的程序?qū)Π托良△喙泛推渌分g的行為差異進(jìn)行了建模。如果你不知道什么是巴辛吉小鬣狗,那么我告訴你,這是一種產(chǎn)自非洲的小型卷尾狗,它們從來都不叫喚。那么,這個(gè)程序?qū)⒋蛴〕鍪裁茨兀?BR> class Dog {
public static void bark() {
System.out.print("woof ");
}
}
class Basenji extends Dog {
public static void bark() { }
}
public class Bark {
public static void main(String args[]) {
Dog woofer = new Dog();
Dog nipper = new Basenji();
woofer.bark();
nipper.bark();
}
}
隨意地看一看,好像該程序應(yīng)該只打印一個(gè)woof。畢竟,Basenji擴(kuò)展自Dog,并且它的bark方法定義為什么也不做。main方法調(diào)用了bark方法,第一次是在Dog類型的woofer上調(diào)用,第二次是在Basenji類型的nipper上調(diào)用。巴辛吉小鬣狗并不會(huì)叫喚,但是很顯然,這一只會(huì)。如果你運(yùn)行該程序,就會(huì)發(fā)現(xiàn)它打印的是woof woof。這只可憐的小家伙到底出什么問題了?
問題在于bark是一個(gè)靜態(tài)方法,而對(duì)靜態(tài)方法的調(diào)用不存在任何動(dòng)態(tài)的分派機(jī)制[JLS 15.12.4.4]。當(dāng)一個(gè)程序調(diào)用了一個(gè)靜態(tài)方法時(shí),要被調(diào)用的方法都是在編譯時(shí)刻被選定的,而這種選定是基于修飾符的編譯期類型而做出的,修飾符的編譯期類型就是我們給出的方法調(diào)用表達(dá)式中圓點(diǎn)左邊部分的名字。在本案中,兩個(gè)方法調(diào)用的修飾符分別是變量woofer和nipper,它們都被聲明為Dog類型。因?yàn)樗鼈兙哂邢嗤木幾g期類型,所以編譯器使得它們調(diào)用的是相同的方法:Dog.bark。這也就解釋了為什么程序打印出woof woof。盡管nipper的運(yùn)行期類型是Basenji,但是編譯器只會(huì)考慮其編譯器類型。
要訂正這個(gè)程序,直接從兩個(gè)bark方法定義中移除掉static修飾符即可。這樣,Basenji中的bark方法將覆寫而不是隱藏Dog中的bark方法,而該程序也將會(huì)打印出woof,而不是woof woof。通過覆寫,你可以獲得動(dòng)態(tài)的分派;而通過隱藏,你卻得不到這種特性。
當(dāng)你調(diào)用了一個(gè)靜態(tài)方法時(shí),通常都是用一個(gè)類而不是表達(dá)式來標(biāo)識(shí)它:例如,Dog.bark或Basenji.bark。當(dāng)你在閱讀一個(gè)Java程序時(shí),你會(huì)期望類被用作為靜態(tài)方法的修飾符,這些靜態(tài)方法都是被靜態(tài)分派的,而表達(dá)式被用作為實(shí)例方法的修飾符,這些實(shí)例方法都是被動(dòng)態(tài)分派的。通過耦合類和變量的不同的命名規(guī)范,我們可以提供一個(gè)很強(qiáng)的可視化線索,用來表明一個(gè)給定的方法調(diào)用是動(dòng)態(tài)的還是靜態(tài)的。本謎題的程序使用了一個(gè)表達(dá)式作為靜態(tài)方法調(diào)用的修飾符,這就誤導(dǎo)了我們。千萬不要用一個(gè)表達(dá)式來標(biāo)識(shí)一個(gè)靜態(tài)方法調(diào)用。
覆寫的使用與上述的混亂局面攪到了一起。Basenji中的bark方法與Dog中的bark方法具有相同的方法簽名,這正是覆寫的慣用方式,預(yù)示著要進(jìn)行動(dòng)態(tài)的分派。然而在本案中,該方法被聲明為是static的,而靜態(tài)方法是不能被覆寫的;它們只能被隱藏,而這僅僅是因?yàn)槟銢]有表達(dá)出你應(yīng)該表達(dá)的意思。為了避免這樣的混亂,千萬不要隱藏靜態(tài)方法。即便在子類中重用了超類中的靜態(tài)方法的名稱,也不會(huì)給你帶來任何新的東西,但是卻會(huì)喪失很多東西。
對(duì)語言設(shè)計(jì)者的教訓(xùn)是:對(duì)類和實(shí)例方法的調(diào)用彼此之間看起來應(yīng)該具有明顯的差異。第一種實(shí)現(xiàn)此目標(biāo)的方式是不允許使用表達(dá)式作為靜態(tài)方法的修飾符;第二種區(qū)分靜態(tài)方法和實(shí)例方法調(diào)用的方式是使用不同的操作符,就像C++那樣;第三種方式是通過完全拋棄靜態(tài)方法這一概念來解決此問題,就像Smalltalk那樣。
總之,要用類名來修飾靜態(tài)方法的調(diào)用,或者當(dāng)你在靜態(tài)方法所屬的類中去調(diào)用它們時(shí),壓根不去修飾這些方法,但是千萬不要用一個(gè)表達(dá)式去修飾它們。還有就是要避免隱藏靜態(tài)方法。所有這些原則合起來就可以幫助我們?nèi)ハ切┤菀琢钊苏`解的覆寫,這些覆寫需要對(duì)靜態(tài)方法進(jìn)行動(dòng)態(tài)分派。
public static void bark() {
System.out.print("woof ");
}
}
class Basenji extends Dog {
public static void bark() { }
}
public class Bark {
public static void main(String args[]) {
Dog woofer = new Dog();
Dog nipper = new Basenji();
woofer.bark();
nipper.bark();
}
}
隨意地看一看,好像該程序應(yīng)該只打印一個(gè)woof。畢竟,Basenji擴(kuò)展自Dog,并且它的bark方法定義為什么也不做。main方法調(diào)用了bark方法,第一次是在Dog類型的woofer上調(diào)用,第二次是在Basenji類型的nipper上調(diào)用。巴辛吉小鬣狗并不會(huì)叫喚,但是很顯然,這一只會(huì)。如果你運(yùn)行該程序,就會(huì)發(fā)現(xiàn)它打印的是woof woof。這只可憐的小家伙到底出什么問題了?
問題在于bark是一個(gè)靜態(tài)方法,而對(duì)靜態(tài)方法的調(diào)用不存在任何動(dòng)態(tài)的分派機(jī)制[JLS 15.12.4.4]。當(dāng)一個(gè)程序調(diào)用了一個(gè)靜態(tài)方法時(shí),要被調(diào)用的方法都是在編譯時(shí)刻被選定的,而這種選定是基于修飾符的編譯期類型而做出的,修飾符的編譯期類型就是我們給出的方法調(diào)用表達(dá)式中圓點(diǎn)左邊部分的名字。在本案中,兩個(gè)方法調(diào)用的修飾符分別是變量woofer和nipper,它們都被聲明為Dog類型。因?yàn)樗鼈兙哂邢嗤木幾g期類型,所以編譯器使得它們調(diào)用的是相同的方法:Dog.bark。這也就解釋了為什么程序打印出woof woof。盡管nipper的運(yùn)行期類型是Basenji,但是編譯器只會(huì)考慮其編譯器類型。
要訂正這個(gè)程序,直接從兩個(gè)bark方法定義中移除掉static修飾符即可。這樣,Basenji中的bark方法將覆寫而不是隱藏Dog中的bark方法,而該程序也將會(huì)打印出woof,而不是woof woof。通過覆寫,你可以獲得動(dòng)態(tài)的分派;而通過隱藏,你卻得不到這種特性。
當(dāng)你調(diào)用了一個(gè)靜態(tài)方法時(shí),通常都是用一個(gè)類而不是表達(dá)式來標(biāo)識(shí)它:例如,Dog.bark或Basenji.bark。當(dāng)你在閱讀一個(gè)Java程序時(shí),你會(huì)期望類被用作為靜態(tài)方法的修飾符,這些靜態(tài)方法都是被靜態(tài)分派的,而表達(dá)式被用作為實(shí)例方法的修飾符,這些實(shí)例方法都是被動(dòng)態(tài)分派的。通過耦合類和變量的不同的命名規(guī)范,我們可以提供一個(gè)很強(qiáng)的可視化線索,用來表明一個(gè)給定的方法調(diào)用是動(dòng)態(tài)的還是靜態(tài)的。本謎題的程序使用了一個(gè)表達(dá)式作為靜態(tài)方法調(diào)用的修飾符,這就誤導(dǎo)了我們。千萬不要用一個(gè)表達(dá)式來標(biāo)識(shí)一個(gè)靜態(tài)方法調(diào)用。
覆寫的使用與上述的混亂局面攪到了一起。Basenji中的bark方法與Dog中的bark方法具有相同的方法簽名,這正是覆寫的慣用方式,預(yù)示著要進(jìn)行動(dòng)態(tài)的分派。然而在本案中,該方法被聲明為是static的,而靜態(tài)方法是不能被覆寫的;它們只能被隱藏,而這僅僅是因?yàn)槟銢]有表達(dá)出你應(yīng)該表達(dá)的意思。為了避免這樣的混亂,千萬不要隱藏靜態(tài)方法。即便在子類中重用了超類中的靜態(tài)方法的名稱,也不會(huì)給你帶來任何新的東西,但是卻會(huì)喪失很多東西。
對(duì)語言設(shè)計(jì)者的教訓(xùn)是:對(duì)類和實(shí)例方法的調(diào)用彼此之間看起來應(yīng)該具有明顯的差異。第一種實(shí)現(xiàn)此目標(biāo)的方式是不允許使用表達(dá)式作為靜態(tài)方法的修飾符;第二種區(qū)分靜態(tài)方法和實(shí)例方法調(diào)用的方式是使用不同的操作符,就像C++那樣;第三種方式是通過完全拋棄靜態(tài)方法這一概念來解決此問題,就像Smalltalk那樣。
總之,要用類名來修飾靜態(tài)方法的調(diào)用,或者當(dāng)你在靜態(tài)方法所屬的類中去調(diào)用它們時(shí),壓根不去修飾這些方法,但是千萬不要用一個(gè)表達(dá)式去修飾它們。還有就是要避免隱藏靜態(tài)方法。所有這些原則合起來就可以幫助我們?nèi)ハ切┤菀琢钊苏`解的覆寫,這些覆寫需要對(duì)靜態(tài)方法進(jìn)行動(dòng)態(tài)分派。