Abstract Data Types(ADTs)
目前為止我們有了介面(interface)的概念,在介面內定義了一個列表(list)應該要可以使用的方法。並且透過繼承(inheritance)關係,用陣列(array)或是節點(node)的方式實現,而這個沒有記載如何實現列表的介面,又可以稱為抽象資料型態(ADTs)。
取自於 Wiki 上 Java 上的抽象資料型別 Collections 的繼承關係如圖一所示。 從中可以發現實際上繼承的關係遠比我們目前所學更為複雜,不過以我們認識的類別 ArrayList 來說,往父階的方向來看,它繼承了 AbstractList、List、Collection 以及 Iterable。可以發現愈往上層的介面的概念越模糊,換言之,所包含的範圍也愈廣,不過可以確定的是,ArratList 這個類別擁有 List、Collection、Iterable 這三種介面的特性,或著是說,實現了這三種介面所定義的方法。
抽象類別(abstract class)基本上可以完成所有介面可以做到的事情,不過更為彈性,不過在此就不多加贅述。
Map61B
Map 是其中一種常見的抽象資料型態,可以想像為 Python 的字典(dictionary),實現 Map 的方式有很多種,常見會以雜湊(hash)的方式來完成,不過之後才會介紹甚麼是雜湊,暫時先以熟悉的陣列來完成 Map61B 的所有方法:
- put(key, value)
建立一組鍵(key)和值(value)的關係 - containsKey(key)
是否含鍵 - get(key)
回傳鍵對應的值 - keys()
回傳一個列表包含所有的鍵 - size()
回傳鍵的數量
如之前介紹,Map61B 內的鍵和值型態可以是任意型態,運用 <K, V> 的語法,先在類別內定義好所有的 K 為同一個型態;所有的 V 為同一個型態,當然 <K, V> 也可以改成 <potato, banana> 只要在類別內的命名一致即可,不過常用大寫的字母來表示。當實例化類別時,只要指定通型的型態如 <String, Integer>,即可建立一個字串和整數的鍵值關係。
透過通型,允許所有的 Map61B 可以存放任意型態的鍵值關係,只要在實例時指定想要的型態即可。然而,在實際使用上可能會出現預期外的問題,舉例來說建立一個整數對整數的鍵值關係如下所示:
原本預期通過的測試,結果卻在 compile time 出現錯誤,原因是 assertEquals 這個方法有其它多載(overload)例如:
assertEqual(object, object);
assertEqual(long, long);
此時 Java 雖然會自動將 primitive type 轉換成 reference type,不過沒有辦法確定是要將 int 轉換成 Integer,使用方法 assertEqual(Object, Object);或著是將 int 擴充成 long,並將 Integer 轉換成 int 後再擴充成 long,使用方法 assertEqual(long, long)。即使這個測試實際在 dynamic time 確實是比較兩個整數,不過在 compile time 無法確定要使用哪一個多載,所以出現錯誤。其中一個解決的方式是使用重鑄( cast)在 compile time 將 expected 重鑄成 Integer,此時 compiler,就可以確定使用 assertEqual(Object, Object)。
通型方法(Generic methods)
如果在這個類別內使用 get 方法可能會有發生錯誤的風險,原因是當輸入一個不存在 Map 內的鍵,keyIndex 這個私有函數會回傳 -1,而矩陣內並無 -1 這個 index,以至於跳出 ArrayIndexOutOfBoundsException 的錯誤訊息。
此時建立另一個類別 MapHelper 來解決這個問題,並可以使用兩個方法
- get(Map61B, key)
回傳 Map61B 中與鍵對應的值 - maxKey(Map61B)
回傳 Map61B 中最大的鍵
同樣的,會希望 MapHelper 能允許所有類型的鍵值關係,此時不會選擇在類別名稱後使用通型的語法,原因是這代表需要實例化這個類別,並指定特定類型。然而在使用 MapHelper 時,不會希望先實例一個特定類型的 MapHelper,再使用底下的方法。此時 Java 提供另一個通型方法的語法,不在類別上使用通型,而是在方法前加上通型的語法如下:
這裡的 <K, V> 同樣可以替換為任意名稱,Java 會自動推論輸入的類型。
另一個方法 maxKey,直觀上先用一個變數記錄第一個鍵,接著比較剩餘的鍵,只要大於暫存的鍵則更新鍵即可。不過如同先前遇到的問題,不是每一個類型都可以使用大於(>)的運算符,因此會使用 compareTo 的方法來比較大小。然而,並不是每一個類型都擁有 compareTo 這個方法來比較大小,因此在這裡加上一個限制,限制任意類型的鍵都必須實現 Comparable 這個介面,也就是說,任意 K 都可以被比較。
在這裡 extends 所代表的意思並非從介面、或著是父類別繼承所有成員,而是一種限制,換句話說,限制 K 一定要是 Comparable 的子類別,只是使用 extends 這個語法。