下面这个程序的意图是好的,它试图根据一个集合是set,list,还是其他集合类型,来进行分类:
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));
}
}
我们希望打出的是,set,list,Unknown Collection。实际上,它的输出是:
Unknown Collection
Unknown Collection
Unknown Collection
为什么会这样,因为classify放发被重载,而需要调用哪个重载放发是在编译时候做出决定的,对于for循环中的全部三次迭代,参数的编译时类型都是相同的:Collection<?>.,每次迭代的运行时类型都是不同的,但这并不影响对重载方法的选择。
这个程序的行为有悖常理,因为对于重载方法的选择是静态的,而对于被覆盖的方法的选择是动态的,选择被覆盖的方法的正确版本是在运行时进行的,选择的依据是被调用方法所在对象的运行时类型,举个例子
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());
}
}
使用重载的原则
- 永远不要导出两个具有相同参数数目的重载方法。尽量使重载方法的参数数量不同;对于使用的可变参数,最好不要重载。
- 如果一定要重载,那么对于一对重载方法,至少要有一个对应的参数在两个重载方法中的类型“完全不同”。这样一来,就不可以把一种实例转换为另一种实例,相对来说是保守安全的。
针对上面第2条,给出一个典型的错误例子:
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和预期不一致。对于List,有两个重载函数,这里直接重载了list.remove(i),并没有重载到list.remove(E) 纠正如下:
for (int i = 0; i < 3; i++) {
set.remove(i);
list.remove(Integer.valueOf(i));
}