第41条:慎用重载
试图根据一个集合是Set、Liist、还是其他的集合类型,对它进行分类的程序:
public class CollectionClassifier { public static String classify(Set<?> s) { return "Set"; } public static String classify(List<?> l) { 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。classify方法被重载,而要调用哪个重载方法是在编译时做出决定的,而三个集合的参数在编译时的类型都是Collection<?>,虽然运行时的类型不同,但不影响对重载方法的选择。所以唯一适合的方法是参数为Collection<?>的那一个。
与覆盖方法不同,覆盖方法的选择是动态的,选择的依据是运行时的类型,而不管编译时的类型是什么。
修正方案,使用单个方法替换重载的classify方法,也就是不使用重载机制:
public static String classify(Collection<?> c) { return c instanceof Set ? "Set" : c instanceof List ? "List" : Unknown Collection"; }
ObjectOutputStream类,对于每个基本类型,以及几种引用类型,它的write方法的命名都不一样,如writeInt,writeBoolean,writeLong等。这是一种好的实践。
Java 1.5发行后,出现了自动装箱,引起的一些问题:
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); } }
打印的结果是:
原因是List有remove(int)和remove(Object)方法,Set只有一个remove(Object)方法,list.remove(i)调用的是remove(int)方法,所以导致这样的结果。
修改成list.remove((Integer) i);就能使结果一致了。
对于多个具有相同参数数目的方法来说,应该尽量避免重载方法,对于构造器,可以选择静态工厂,避免参数只需经过类型转换就可以被传递给不同的重载方法,如果不能避免,应保证传递相同参数,所有的重载方法行为一致,否则对程序员有效使用重载方法会造成困难。