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());
}
}
}
结果:第一个: 三个Unknown Collection
第二个:wine, sparkling wine, champagne
这里实际上是把函数的重载和多态混淆了。方法的覆盖(重写)用来实现多态,这才是动态的,在运行时选择被覆盖的方法。而重载不一样,对象的运行时类型并不影响“哪个重载版本将被执行”,对于重载方法的选择是静态的,要调用哪个重载方法是在编译时就已经决定的。上述例子中,循环中的三个类,编译器都认为是Collection<?>类,所以调用的是classify(Collection<?> c)方法。
修正第一个例子的方法就是用一个方法来代替这三个重载的方法
public static String classify(Collection<?> c) {
return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";
}```
如果对于API来说,普通用户根本不知道"对于一组给定的参数,其中那个重载方法将会被调用",那么这样的API就很容易出错。而且这类错误只有等到程序出现非常怪异的行为的时候才能被发现,而且不容易诊断错误。因此,尽量避免胡乱地使用重载机制。
到底怎样才算胡乱使用重载机制这个问题还存在争议,我们建议的安全而保守的策略是:永远不要导出两个具有相同参数数目的重载方法。如果方法使用可变参数,根本不要重载它(除书中42条描述的情形)就是说重载时尽量使重载方法的参数数量不同,而对于可变参数的方法就不要重载了。如果遵守这些限制,我们就不会疑问到底会调用哪个重载方法。可以给不同的重载机制起不同的方法名称(这个应该就不是重载了)。例如ObjectOutputStream类
这里要特别注意的是类的构造器,因为你不可能吧构造器重新命名!可以考虑导出静态工厂。对于导出多个具有相同参数数目的重载方法时至少有一个对应参数在两个重载方法中具有"根本不同的类型"。这样会避免混淆。
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);
}
}```
结果:[-3, -2, -1] [-2, 0, 2]
Set的输出结果如同我们想的一样,但是List的结果不一样。实际发生的情况是:set.remove(E),选择重载方法的参数实际上是Integer,这里进行了自动装箱,把int装箱成了Integer;对于List,有两个重载函数,这里直接重载了list.remove(i),并没有重载到list.remove(E),是从list的指定位置进行remove,所以先移除了第0个,也就是-3,list中所有元素前移;再移除第1个,也就是list中当前第2个,也就是-1;以此类推,最后得到-2,0,2。这个是jdk1.5之后自动装箱拆箱导致的重载混淆问题,它破坏了List接口。
#总结
- 应该避免胡乱的使用重载机制
- 永远不要导出两个具有相同参数数目的重载方法。如果方法使用可变参数,根本不要重载它
- 对于导出多个具有相同参数数目的重载方法时至少有一个对应参数在两个重载方法中具有"根本不同的类型"。
- 另外一些情况下,重载方法尽量把调用转发给一般的重载方法去做,不同的重载方法尽量保证行为一致。(构造)
网友评论