參考書籍:《Data Structures and Algorithm Analysis in Java》3rd — Mark Allen Weiss
動機:
在一組數據中取出特定值,這就是選擇問題。選擇問題的解法非常多,但能夠在實踐中能有效地運行下去卻不多。我們必須不斷改進並突破程序運行的速度、瓶頸,才能應付規模日漸龐大的數據。
基礎知識複習:
-
遞歸
四條基本法則:
- 基準情形:必須有一個無需遞歸即可知道的先驗基準。如
- 不斷推進:遞歸過程必須不斷向基準情形靠近
- 設計法則:所有的遞歸情況都應該是合法的
- 合成效益法則:不應該在遞歸中多次重複同一工作
- 基準情形:必須有一個無需遞歸即可知道的先驗基準。如
-
泛型
pre-Java 5:使用類繼承的方法實現泛型
基本思想:用Object這個超類表示所有的類型。如果是基本類型,就需要包裝類如Integer(在Java 5+支持自動裝箱/拆箱)
問題:僅僅只是表示以及調用一些Object方法並不能滿足所有的需求,如比較兩個類型的Value
解決方法:繼承Comparable接口,該接口需要實現compareTo(T o)
函數。這種方法涉及到對原類代碼的擴展,可以創建子類實現接口或者取得源代碼。創建子類或許會破壞原項目的接口協議,而即使能夠取得源代碼且有理由進行修改-------僅有的一個compareTo
函數依然只能支持對類的有限實例變量進行比較。爲了能夠自由地對類的各種實例變量進行比較,可以實現Class::Comparator
作爲比較類因子。要進行多少種不同比較需求就創建相應數目的比較類即可。
要點:
(1)只有實現了Comparable接口的類纔是被接收的-------不管這個接口是官方定義還是個人定義的接口
(2)包裝類已實現官方接口但基礎類型不能實現因此必須進行包裝
Java 5+:提供對泛型方法和類的支持
數組兼容性:
由於多態性,函數能夠接受子類數組作爲參數傳入,但是這可能導致數組被污染----如果代碼中涉及到元素的添加。
例如public void go(){ Dog[] dogs = {new Dog(), new Dog(), new Dog()}; takeAnimals(dogs); } public void takeAnimals(Animal[] animals) { animals[0] = new Cat(); }
在這種情況下,編譯時就會發生錯誤。如果是ArrayList之類的容器,則能過編譯,但是運行時報錯。可以用萬用字符/通配符 ? 作爲容器類型,使用這個字符後,編譯器會在編譯時阻止任何企圖向父類容器中加入新元素的語句,也就是說,只能調用方法,但不能添加刪除元素。
類型界限:
作用是限制泛型必須實現某個接口或繼承某個類(都是用extends)最簡單的形式:
<T extends Comparable>
但這樣更好,限制更明確:
<T extends Comparable<T>>
這樣最好:
<T extends Comparable<? super T>>
這種聲明的好處在於:如果T因爲某種原因沒有實現Comparable,但是他的父類實現了且滿足T這個子類的實際需求,那麼**? super T**就指示編譯器:T或T的任意父類K實現 Comparable<T> 接口就行了,T本身可以不實現。
類型擦除:
在進入JVM之前,泛型會被轉譯爲object型或者類型界限(extend/super) 。這種特性可以得出一個推論:使用泛型和不使用泛型(單純利用多態)其本質運行速度並不會有太大區別,而使用泛型的好處就在於程序員不必自己在代碼中進行多態的類型檢驗
泛型限制:-
不能使用基本類型作爲類型參數
-
類型檢測只會對原始類型進行檢測,如果用到泛型容器->那麼可以隨意地轉換類型,然而轉換後的方法調用卻可能會出錯
-
static語境
不能在static方法中使用類的泛型類型,因爲static方法全局只有一個,不知道該使用哪個實例的類型 -
泛型類型實例化
不能用泛型類型進行實例化,如T obj = new T();
這是因爲類型擦除後可能無法調用限界類的構造函數(如私有或無意義)
-
泛型數組對象
不能創建泛型數組,原因是類型擦除後上下文不一致 -
參數化類型的數組
-
-
函數對象
一個沒有數據而只有一個方法的類,被稱作函數對象。創建對象的目的就是爲了把函數作爲參數傳遞進別的函數中。典型的例子就是Comparator比較類因子