第41條:慎用重載

public class CollectionClassifier {
    public static String classify(Set<?> s) {
        return "Set";
    }
    public static String classify(List<?> lst) {
        return "List";
    }
    public static String classify(Collection<?> c) {
        return "Unknown Collection";
    }
    public static void main(String[] args) {
        Collection<?>[] collections = { new HashSet<String>(), new ArrayList<BigInteger>(),
                new HashMap<String, String>().values() };
        for (Collection<?> c : collections) {
            System.out.println(classify(c));
        }
    }
}```

class Wine {
String name() { return "wine"; }
}
class SparklingWine extends Wine {
@Override String name() { return "sparkling wine"; }
}
class Champagne extends SparklingWine {
@Override String name() { return "champagne"; }
}
public class Overriding {
public static void main(String[] args) {
Wine[] wines = {
new Wine(), new SparklingWine(), new Champagne()
};
for (Wine wine : wines) {
System.out.println(wine.name());
}
}
}

結(jié)果:第一個(gè): 三個(gè)Unknown Collection
   第二個(gè):wine, sparkling wine, champagne
  這里實(shí)際上是把函數(shù)的重載和多態(tài)混淆了。方法的覆蓋(重寫)用來實(shí)現(xiàn)多態(tài),這才是動(dòng)態(tài)的,在運(yùn)行時(shí)選擇被覆蓋的方法。而重載不一樣,對(duì)象的運(yùn)行時(shí)類型并不影響“哪個(gè)重載版本將被執(zhí)行”,對(duì)于重載方法的選擇是靜態(tài)的,要調(diào)用哪個(gè)重載方法是在編譯時(shí)就已經(jīng)決定的。上述例子中,循環(huán)中的三個(gè)類,編譯器都認(rèn)為是Collection<?>類,所以調(diào)用的是classify(Collection<?> c)方法。
  修正第一個(gè)例子的方法就是用一個(gè)方法來代替這三個(gè)重載的方法 

public static String classify(Collection<?> c) {
return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";
}```
  如果對(duì)于API來說,普通用戶根本不知道"對(duì)于一組給定的參數(shù),其中那個(gè)重載方法將會(huì)被調(diào)用",那么這樣的API就很容易出錯(cuò)。而且這類錯(cuò)誤只有等到程序出現(xiàn)非常怪異的行為的時(shí)候才能被發(fā)現(xiàn),而且不容易診斷錯(cuò)誤。因此,盡量避免胡亂地使用重載機(jī)制。
  到底怎樣才算胡亂使用重載機(jī)制這個(gè)問題還存在爭(zhēng)議,我們建議的安全而保守的策略是:永遠(yuǎn)不要導(dǎo)出兩個(gè)具有相同參數(shù)數(shù)目的重載方法。如果方法使用可變參數(shù),根本不要重載它(除書中42條描述的情形)就是說重載時(shí)盡量使重載方法的參數(shù)數(shù)量不同,而對(duì)于可變參數(shù)的方法就不要重載了。如果遵守這些限制,我們就不會(huì)疑問到底會(huì)調(diào)用哪個(gè)重載方法??梢越o不同的重載機(jī)制起不同的方法名稱(這個(gè)應(yīng)該就不是重載了)。例如ObjectOutputStream類

write.png

這里要特別注意的是類的構(gòu)造器,因?yàn)槟悴豢赡馨蓸?gòu)造器重新命名!可以考慮導(dǎo)出靜態(tài)工廠。對(duì)于導(dǎo)出多個(gè)具有相同參數(shù)數(shù)目的重載方法時(shí)至少有一個(gè)對(duì)應(yīng)參數(shù)在兩個(gè)重載方法中具有"根本不同的類型"。這樣會(huì)避免混淆。

public class SetList {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<Integer>();
        List<Integer> list = new ArrayList<Integer>();
        for (int i = -3; i < 3; i++) {
            set.add(i);
            list.add(i);
        }
        for (int i = 0; i < 3; i++) {
            set.remove(i);
            list.remove(i);
        }
        System.out.println(set + " " + list);
    }
}```
結(jié)果:[-3, -2, -1] [-2, 0, 2]
  Set的輸出結(jié)果如同我們想的一樣,但是List的結(jié)果不一樣。實(shí)際發(fā)生的情況是:set.remove(E),選擇重載方法的參數(shù)實(shí)際上是Integer,這里進(jìn)行了自動(dòng)裝箱,把int裝箱成了Integer;對(duì)于List,有兩個(gè)重載函數(shù),這里直接重載了list.remove(i),并沒有重載到list.remove(E),是從list的指定位置進(jìn)行remove,所以先移除了第0個(gè),也就是-3,list中所有元素前移;再移除第1個(gè),也就是list中當(dāng)前第2個(gè),也就是-1;以此類推,最后得到-2,0,2。這個(gè)是jdk1.5之后自動(dòng)裝箱拆箱導(dǎo)致的重載混淆問題,它破壞了List接口。

#總結(jié)
- 應(yīng)該避免胡亂的使用重載機(jī)制
- 永遠(yuǎn)不要導(dǎo)出兩個(gè)具有相同參數(shù)數(shù)目的重載方法。如果方法使用可變參數(shù),根本不要重載它
- 對(duì)于導(dǎo)出多個(gè)具有相同參數(shù)數(shù)目的重載方法時(shí)至少有一個(gè)對(duì)應(yīng)參數(shù)在兩個(gè)重載方法中具有"根本不同的類型"。
- 另外一些情況下,重載方法盡量把調(diào)用轉(zhuǎn)發(fā)給一般的重載方法去做,不同的重載方法盡量保證行為一致。(構(gòu)造)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容