Effective Java 读书笔记——41:慎用重载
来源:互联网 发布:node socket.io聊天室 编辑:程序博客网 时间:2024/06/17 17:04
先看一个使用重载错误的例子:
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 CollectionUnknown CollectionUnknown Collection首先,classify方法在这里被重载了是肯定的,事实上要调用哪个方法进行重载,在编译时就已经做出决定了。循环中的三个类,编译器都认为是Collection<?>类,所以输出的是三个Unknown Collection。
在这里,把函数的重载和多态混淆了。方法的覆盖用来实现多态,这才是动态的,在运行时选择被覆盖的方法。
下面是一个多态的典型正确例子,在调用被覆盖方法的时候,编译的类型不会有影响,根据new的类型的方法将会被执行:
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()); }}
public static String classify(Collection<?> c) {return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";}
如果对于API来说,普通用户根本不知道对于一组给定的参数,到底会重载哪个函数的时候,那么这样的API就很容易出错。而且这类错误只有等到程序出现非常怪异的行为的时候才能被发现,而且不容易诊断错误。因此,尽量避免胡乱使用重载。
使用重载的原则
- 永远不要导出两个具有相同参数数目的重载方法。尽量使重载方法的参数数量不同;对于使用的可变参数,最好不要重载。
- 如果一定要重载,那么对于一对重载方法,至少要有一个对应的参数在两个重载方法中的类型“完全不同”。这样一来,就不可以把一种实例转换为另一种实例,相对来说是保守安全的。
针对上面第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,0,1,2,在第二个循环中,试着删除0,1,2,希望留下-3,-2,-1。可是输出结果是:
[-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。我们可以在源码中看到:
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } /** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> * (if such an element exists). Returns <tt>true</tt> if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return <tt>true</tt> if this list contained the specified element */ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
因此,如果想修正上面的代码,我们可以写成:
for (int i = 0; i < 3; i++) {set.remove(i);list.remove(Integer.valueOf(i));}
总结
实际情况往往比上面分析的复杂的多,确定选择哪个重载方法的规则也极其困难。有时候,新增的API可能会违背上面的规则,但是,只要重载的方法执行相同的功能,返回相同的结果,这样也是可以接受的。确保这种行为的标准做法是,让更具体化的重载方法把调用转发给更一般的重载方法去做。
总之,能够重载并不意味者应该重载,一般来说,对于多个具有相同参数数目的重载方法,还是尽量避免使用重载。另外一些情况下,重载方法尽量把调用转发给一般的重载方法去做,不同的重载方法尽量保证行为一致。
0 0
- Effective Java 读书笔记——41:慎用重载
- Effective Java慎用重载
- Effective Java 读书笔记——71:慎用延迟初始化
- Effective Java 读书笔记——42:慎用可变参数
- 慎用重载(effective java)
- 《Effective java》读书笔记6——方法重载
- 《Effective java》—–读书笔记
- 《Effective java》—–读书笔记
- more effective C++的读书笔记 ——不要重载&&和||
- java方法重载(慎用重载)
- 第41条 方法——慎用重载
- 《Effective java》读书笔记4——泛型
- 《Effective java》读书笔记5——枚举
- 《Effective java》读书笔记7——异常
- 《Effective java》读书笔记——过期引用
- 《effective java》读书笔记——(一)
- Effective Java慎用可变参数
- (41):慎用重载
- Linux系统unzip解压后中文名乱码解决方法
- 大数据知识总结
- MaVen介绍
- 绝地武士Obi- Wan Kenobi
- Android中TextView首行缩进
- Effective Java 读书笔记——41:慎用重载
- poj 3190 Stall Reservations
- Pycharm中html模板不能用jinja2标签
- Spotlight 监控工具使用
- 微信小程序支付
- 用c语言实现读取配置文件源码
- According to TLD or attribute directive in tag file, attribute items does not accept any expression
- 蓝桥杯C语言B组-奖券数目
- 使用Maven创建Java项目