Effective_java之二:慎用重載函數

每週寫一篇技術博客的願望一直沒實現, 從這周開始每週五晚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是根據運行時被調用方法所在類實例的類型選擇方法的, 所以會使用子類中被複寫的實現。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章