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類

這里要特別注意的是類的構(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)造)