復用代碼是Java眾多引人注目的功能之一。這句話很通順,沒什么問題,但問題在於很多人並不清楚“復用”是什么。就好像我說“沉默王二是一個不止會寫代碼的程序員”,唉,沉默王二是誰?
我們需要來給“復用”下一個定義。復用,說白了就是重復使用。
舉個例子,很多名人說了很多名言,我們在說話、寫作的時候,就經常有意無意的重復這些名言。比如說我,就特別喜歡重復使用王小波的那句名言:“從話語中,你很少能學到人性,從沉默中卻能。假如還想學得更多,那就要繼續一聲不吭 。”
上面這個例子,只能說是“復用”的一種低級的應用,其實就是復制粘貼了。還有高級的復用方式嗎?
有,當然有。Java作為一種優秀的面向對象設計的語言,在復用的應用上就高級得多了。
01 繼承
最常見的復用方法就是繼承——使用extends
關鍵字在基類的基礎上創建新類,新類可以直接復用基類的非private
的屬性和方法;就像程序清單1-1那樣。
程序清單1-1:
public class Wangxiaosan extends Wangsan {
public Wangxiaosan() {
System.out.println("我是新類王小三");
setName("王老三");
System.out.println(getName());
}
public static void main(String[] args) {
new Wangxiaosan();
}
}
class Wangsan {
private String name;
Wangsan() {
System.out.println("我是基類王三");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
從程序清單1-1中我們可以看得出,getName()和setName()方法雖然是在基類Wangsan中創建的,但可以在新類Wangxiaosan中使用,代碼的復用工作就這樣輕松地完成了。
02 組合
另外一種常見的復用方法就是組合——在新類中創建已有類的對象,通過該對象來調用已有類中的非private的屬性和方法;就像程序清單2-1那樣。
程序清單2-1:
public class Tongxiangyu {
private Baizhantang boyFriend = new Baizhantang();
public Tongxiangyu() {
System.out.println("我是同福客棧的掌櫃佟湘玉");
boyFriend.pointHand("郭芙蓉");
}
public static void main(String[] args) {
new Tongxiangyu();
}
}
class Baizhantang {
Baizhantang() {
System.out.println("我是退隱江湖的盜聖白展堂");
}
public void pointHand(String name) {
System.out.println("那誰" + name + ",准備一下——葵花點穴手");
}
}
從程序清單2-1中我們可以看得出,葵花點穴手雖然是白展堂的絕技,但作為佟掌櫃的男朋友,佟掌櫃要展堂點個穴,展堂也是不敢推辭的。你看,佟掌櫃雖然是個弱女子,但自從有了展堂這個武功數一數二的男朋友,再沒有誰敢不聽話啊——厲害的組合啊。
需要注意的是,如何在繼承和組合之間做出選擇呢?
如果新類和已有類需要具有一些相似的方法和屬性時,就采用繼承的形式;如果新類只是為了借用已有類的一些方法和屬性時,而兩者沒有很多相似之處時就需要采用組合的形式。
03 代理
還有一種復用方法是代理——在新類中創建代理,通過代理來操作已有類的非private的屬性和方法;就像程序清單3-1那樣。
程序清單3-1:
public class Member {
public static void main(String[] args) {
Proxy proxy = new Proxy();
System.out.println("代理說一個葯丸十五塊");
proxy.buy(15);
}
}
class Proxy {
private Shop shop = new Shop();
public void buy(int money) {
System.out.println("一個葯丸十五塊");
shop.sale(money - 5);
}
}
class Shop {
public void sale(int money) {
System.out.println("一個葯丸十塊錢");
}
}
從程序清單3-1中我們可以看得出,代理的模式和組合有點類似,但又有差別——代理成功的隔開了新類(會員)和已有類(店鋪)的直接關系,使得已有類的方法不直接暴露在新類面前(組合的方式會將已有類的非private的方法和屬性直接暴露在新類中);與此同時,代理拿到了足夠的好處。
04 final
作為代碼的生產者來說,我們有時候希望代碼被復用,有的時候又希望代碼不被復用。當我們不想代碼被復用時,final關鍵字就派上用場了。final這個關鍵字很形象,它本身就說明了一切——最后的,最終的;決定性的;不可更改的。
使用final的場景有三種,分別是數據、方法和類。我們來稍作說明。
1)final 數據
最常見的final數據就是常量了,例如:
public class Consts {
public static final String CMOWER = "沉默王二";
}
對於常量來說,它對於整個應用內的所有類都是可見的,因此是public的;它可以直接通過類名.常量名訪問,所以是static的;它是不可修改的,因此是final的。
另外一種常見的final數據就是參數了,參照程序清單4-1。
程序清單4-1:
public class Cmower {
public void write(final String content) {
// content += "猶未雪"; // final修飾的參數是無法在方法內部被再次修改的
System.out.println(content);
}
public void write1(String content) {
content += "猶未雪";
System.out.println(content);
}
public static void main(String[] args) {
Cmower cmower = new Cmower();
cmower.write("精忠報國");
cmower.write1("靖康恥");
}
}
2)final 方法
在Java類中,所有的private方法都隱式地指定為final的(也就是說,如果你在private方法上加上final修飾符,其實是沒啥意義的)。在介紹繼承的時候,你應該注意到我強調的一句話,就是新類可以直接復用基類的非private的屬性和方法,也就是說private方法是無法被繼承者修改的,因為private方法是final的。
來看程序清單4-2,你會發現Wangsan類型的san引用是不能調用say(String words)方法的,因為private方法是無法被繼承者修改的,盡管Wangxiaosan中重新定義了say(String words)方法。
程序清單4-2:
public class Wangxiaosan extends Wangsan {
public Wangxiaosan() {
say("吃中飯沒");
}
public void say(String words) {
System.out.println("王小三在說:" + words);
}
public static void main(String[] args) {
Wangsan san = new Wangxiaosan();
// san.say("吃晚餐沒"); // 無法訪問,並不會被覆蓋
}
}
class Wangsan {
public Wangsan() {
say("吃早飯沒");
}
private void say(String words) {
System.out.println("王三在說:" + words);
}
}
3)final 類
當我們認為某個類就是最終的形態了,它很完美,不應該被繼承,就可以使用final關鍵字來修飾;參照程序清單4-3。
程序清單4-3:
// 無法繼承
public class Wangxiaosan extends Wangsan {
}
final class Wangsan {
public Wangsan() {
System.out.println("我就是最終形態,別繼承我!");
}
}