每週寫一篇技術博客的願望一直沒實現, 從這周開始每週五晚10點是寫博客的時間
OOP的一個重要特性就是多態,實現多態的目的有多種途徑。比如:重載overload、重寫overwite、面向接口編程等等。但是在實際應用中應該慎用重載,這也是Effective Java中提到的一條。下面先展示下eJava中提到的代碼:
@Test
public void testOverWrite(){
List<Integer> intList = new ArrayList<Integer>();
Set<Integer> intSet = new HashSet<Integer>();
for(int i = -3 ; i < 3 ; i++){
intList.add(i);
intSet.add(i);
}
System.out.println(intList+" ---> "+intSet);
for(int i =0 ; i< 3 ;i++){
intList.remove(i);
intSet.remove(i);
}
System.out.println(intList+" ### "+intSet);
}
如果沒有test的話可能很多人會以爲輸出這樣吧:
[-3, -2, -1, 0, 1, 2] ---> [0, 1, 2, -3, -2, -1]
[-3, -2, -1] ### [-3, -2, -1]
但是結果卻是這樣的:
[-3, -2, -1, 0, 1, 2] ---> [0, 1, 2, -3, -2, -1]
[-2, 0, 2] ### [-3, -2, -1]
第一行肯定都沒問題,intSet也沒問題。intList可能很多人會有疑問了‘’爲什麼跟intSet不一樣了‘
其實在JDK5之前也沒這問題,jdk5及以後增加了自動封裝箱的功能,基本類型和對引用類型會自動幫你轉換。
這樣就導致了List在remove的時候移除的是索引,而不是你以爲的容器內的數據。
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; // Let gc do its work
return oldValue;
}
而非這個函數:
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;
}
jdk自動幫你解裝箱了。而HashSet沒有remove索引的方法所以調用了是remove對象
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
因此不會出現list那種問題。
所以當List remove一個Integer對象的時候需要注意,很可能結果不是你想要的功能。
-------------------------漂亮的分割線——————————————————
二、當參數列表類似時,最好不要用重載。特別是導出公共的API。最容易是 使用者 造成困惑。如我今天遇到的一公共Money類中有兩個參數列表相同的函數:multiply和multiplyBy,擁有相同的參數列表。首次使用時跟進去仔細開了代碼,記住了multiply內部是新new了個對象,原來對象的值不變。也理解了這個值是不能改變的。但是這次上線前優化了行代碼,使用了’multiply‘.測試時只跟進了上半部分,發現數據是對的。結果最後又問題了,最後發現使用了是multiplyBy,而該函數是改變原來對象的。浪費了一時間。爲什麼不寫全稱呢?一個函數名大概最多可以用65535個字符長度,貌似再複雜的業務函數名也用不了這麼長吧。
————————華麗的分割線————————————————-———
三、觀察代碼:
private static void printClassName(Set<?> set){
System.out.println(set.getClass().getSimpleName());
}
private static void printClassName(List<?> list){
System.out.println(list.getClass().getSimpleName());
}
private static void printClassName(Collection<?> col){
System.out.println("unknow class name...");
}
public static void main(String[] args) {
String[] str = {"a","b"};
Collection<Integer>[] cols = {
new HashSet<Integer>(),
new ArrayList<Integer>(),
new HashMap<Integer,Integer>()
};
for(Collection col : cols){
printClassName(col)
}
}
overwiter是在父子類間實現,overload是在同一個類中實現。所以overload是編譯期決定的。根據引用的類型決定調用哪個方法。所以上述三次都會打印’unknow class name‘.因爲編譯器col都是collection類型的。
而overload是根據運行時被調用方法所在類實例的類型選擇方法的, 所以會使用子類中被複寫的實現。