泛型(Generics)
泛型, 叫廣泛的類型, 透過型別的參數化來達成. 使用泛型的原因, 使用如下二個例子說明
例子1 :減少重複撰寫程式碼
class Pikachu{} class Eve{} //末使用泛型 class PikachuGroup{ private Pikachu[]array; public Pikachu[] getArray(){ return array; } } class EveGroup{ private Eve[]array; public Eve[] getArray(){ return array; } } //上述二個xxxGroup, 可以改寫成如下一個 //使用泛型 class Group<T>{ private T[] array; public T[] getArray(){ return array; } }
而在main裏, 可以寫成如下
public class JavaApplication1 { public static void main(String[] args) { PikachuGroup pg1=new PikachuGroup(); Group<Pikachu> pg2=new Group<>(); Pikachu[] array1=pg1.getArray();//返回Pikachu[]的型態 Pikachu[] array2=pg2.getArray();//同樣是返回Pikachu[]的型態 Group<Eve> eg3=new Group<>(); Eve[] array3=eg3.getArray();//此時返回Eve[]的型態 } }
pg2及eg3, 同樣是 Group的型態, 但pg2.getArray()及eg3.getArray()後, 確是傳回不同的型態
例子2
未使用泛型
下面的程式有二個問題
明明就知道要放字串了, 為何藍字的地方還要轉型成String
可不可以限定只能放字串
public class GenericsTest {
public static void main(String[] args) {
List list=new ArrayList();
list.add("Thomas");
list.add("John");
list.add("Merry");
list.add(new Integer(10));//不會錯
for (Object o:list){
String d=(String)o;//轉到Integer時發生RuntimeError
System.out.println(d);
}
}
}
使用泛型
下面的程式碼改良了程式設計師的心聲, 取出時不需轉型, 且藍色那一行指定只能存入String, 這個過程叫泛化
public class GenericsTest { public static void main(String[] args) { List<String> list=new ArrayList<>(); list.add("Thomas"); list.add("John"); list.add("Merry"); list.add(new Integer(100));//Compile error for (String o:list){ System.out.println(o); } } }
泛化並不是在Runtime時期處理的, 而是在編譯時期就動了手腳, 所以上述紅色那行, 可以很輕易的在編譯期就找出錯誤
泛化一詞, 是指在編譯時期, 就產生一個專屬的類別, 產生此專屬類別這個動作, 稱為泛化.
如果使用Vector v=new Vector();編譯器會提出警告–Obsolete collection. 阿諾的名言~~I am old, but not obsolete
參數化
泛型變數 <T> 代表任何型態. 任何變數名稱都可以使用, 不是一定要用<T>, 但通常有不成文的規定如下
T : type
E: element
K: Key
V:Value
S, U第二第三型態
鑽石運算符 – Diamond <>
List<String> list=new ArrayList<String>(); 可改良為
List<String> list=new ArrayList<>(); <==右邊不需指明型態, JDK7.0才支援
泛型與原生資料
泛型不能接收原生基本資料, 所以只能將原生資料包裝成類別
List<Integer> list=new ArrayList<>();
Iterator與泛型
Iterator本身也是支援泛型的
Iterator<String> it=list.iterator(); while(it.hasNext()){ System.out.println(it.next()); }
方法回傳值的泛化
如下例子, 方法的傳回值也是泛化的
public static List<String> getList(){
List list=new ArrayList<>();
list.add("Thomas");
list.add("John");
list.add("Merry");
return list;
}
泛型多型
泛型多型的轉換, 只允許基底型別的轉換, 參數型別必需是一模一樣的
Vector<Integer> v1=new Vector<>();
List<Integer> v2=v1; //ok
Vector<Integer> v1=new Vector<>();
Vector<Number>v2=v1; //編譯錯誤
泛型萬用型別<?>
應用在方法的接收參數. 方法會接受來自不同的呼叫者, 而每個呼叫者的型態都不一樣時, 就可以使用<?>
public class GenericsTest { public static void main(String[] args) { List<String> list1=new ArrayList<>(); list1.add("Thomas"); list1.add("John"); list1.add("Merry"); List<Integer> list2=new ArrayList<>(); list2.add(5); list2.add(10); list2.add(15); printList(list1); printList(list2); } public static void printList(List<?> list){ Iterator<?> it=list.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } }
上述其實不需要 <?>, 可以改成 public static void printList(List list)
限制型別參數
public static void printList(List<? extends Number>list)
以上, 就只有Integer, Float等才可以傳入printList
另外也可以寫成
public static List<A extends Number> printList(List<A>list){}
這只是用A來取代 “?” 而以, 用AA, AAA都可以
集合(Collections)
以往要將原生資料或物件放入陣列中, 陣列的長度需要宣告, 一宣告後就不能再變更. 所以就產生了集合, 可以隨時變更長度
集合中的每個物件被稱為元素(elements), 每個元素的型態可以不一樣, 因為都會被轉換成Object(未使用泛型時). 不過原生資料不允許被在集合之中
集合的重點在於如何新增移除元素, 如何找到並取出元件, 以及走訪整個集合
集合重度依靠泛型而寫成的, 所以就不再限定只會轉成Object了
Collection Type
Map Type
Collection介面有三個子類別, 分別是LIst, Queue及Set
Map介面是另一個獨立的介面, 跟Collection無關
Collection介面定義了add, addAll, clear, contains, wquals, remove, removeAll, size, iterator等相關方法
Iterator介面
Iterator為Collection的走訪器, 有hasNext(), next(), remove()等方法, 只能由上往下走. 若要能往上讀取, 就要使用ListIterator
ListIterator繼承Iterator, 可作新增修改刪除動作, 每個元素間都有index, 可用index取得元素, 有add , hasNext, hasPrevious, next, nextIndex, previous, previousIndex, remove, set等方法
Enumeration介面
為Map的走訪器, 有hasMoreElements, nextElement等方法
集合四特性
了解每一個集合時, 需記住每個集合是否有如下四個特性
排序性 : 遞增或遞減的特性
順序性 : 是否有依加入的順序排列. 有順序就無排序, 有排序就無順序
重複性 : 是否允許出現重複的物件
鍵值 (Key/value) : 使用鍵值存放物件. 只有Map才是使用此法
集合介面 | 排序性 | 順序性 | 不允許重複 | 使用鍵值 |
ArrayList | v | |||
LinkedList | v | |||
Vector | v | |||
HashSet | v | |||
LinkedHashSet | v | v | ||
TreeSet | v | v | ||
HaspMap | v | |||
LinkedHashMap | v | v | ||
HashTable | v | |||
TreeMap | v | v |
Set介面
由上表可知, 所有的Set都不允許重複物件, 只能包含獨一無二的元素. 實作上, 是使用equals來判斷加入的元素是否重複.
HashSet
無順序, 無排序, 不可重複
HashCode(雜湊碼)是一種數學演算法, HashSet會用此hashcode查找裏面的元件
Set<String> set=new HashSet<>(); set.add("one"); set.add("two"); set.add("three"); set.add("three");//不會新增, 也不會錯誤 Set<String>s1=new HashSet<>(); s1.add("Mercury"); s1.add("Venus"); s1.add("Earth"); s1.add("Mars"); s1.add("Jupiter"); for (String s:s1){ System.out.printf("%s\n", s); } Iterator it=set.iterator(); while(it.hasNext()){ System.out.prinff("%s\n", it.next()); } 結果 : Earth Mars Jupiter Venus Mercury
LinkedHashSet
使用雙向鏈結, 所以具順序性, 無排序, 不允許重複. 新增修改的效能略遜於HashSet(因為要加雙向連結)
Set<String>s2=new LinkedHashSet<>(); s2.add("Mercury"); s2.add("Venus"); s2.add("Earth"); s2.add("Mars"); s2.add("Jupiter"); for (String s:s2){ System.out.printf("%s\n", s); } 結果 : Mercury Venus Earth Mars Jupiter
若要將HashSet依順序取出的話, 可轉為LinkedHashSet類型, 如下
HashSet hs=new HashSet();
LinkedHashSet lhs=new LinkedHashSet(hs);
TreeSet
繼承SortedSet, 使用二元樹排序, 所以具排序性, 無順序, 不可重複, 新增修改的效能是最差的
List介面
最像陣列的集合, 每個集合都有index, 除了可動態擴充長度外, 其他都跟陣列一樣
所有的List都是有順序性, 無排序, 可重複, 可使用add, get, remove, indexOf
ArrayList
最常用的, 使用add新增, get取出
非泛型ArrayList
class OldStyleArrayList{ public void static main(String[] args){ List list=new ArrayList(3); list.add(new Integer(1111)); list.add(new Integer(2222)); list.add(new Integer(3333)); list.add("Oops a string"); Iterator i=list.iterator(); while(i.haveNext()){ Integer o=(Integer)(i.next()); // Runtime error; } } }
泛型ArrayList
class GenericStyleArrayList{
public void static main(String[] args){
List<Integer> list=new ArrayList<>(3);
list.add(new Integer(1111));
list.add(new Integer(2222));
list.add(new Integer(3333));
list.add("Oops a string"); //Compile error
Iterator i=list.iterator();
while(i.haveNext()){
Integer o=i.next();
}
}
}
非泛型, 會Runtime error, 且必需要強制轉型
泛型, 會Compiler error, 不需強制轉型
Vector
最原始的集合, 特性同ArrayList. 但Vector是Thread-safe, 效能比ArrayList差, 真的有需求才使用
Stack
繼承Vector, 具後進先出原則, 使用push加入, pop取出(並刪除), peek取出(但不刪除)
LinkedList
ArrayList因為是採線性儲存, 在插入及刪除時, 需大量移動元素, 效能極差. 而LinkedList採用雙向鏈結, 所以在新增刪除的效能比ArrayList快很多, 但走訪的效能比ArrayList差.
Queue
具先進先出原則, 但不要使用add, remove方法, 因為會丟出例外. 需改採offer, poll, peek三個方法
注意一下, 實作Queue介面的, 其實是LinkedList, 所以LinkedList除了可以當作List來使用, 也可以當成Queue
Queue q=new LinkedList();
q.offer(“First”);
q.poll();
PriorityQueue
是一個可以自訂排序方式的類別, 要怎麼排序, 就先實作Comparator裏的compare()方法, 再將產生的物件放入PriorityQueue的建構子即可
public class CollectionTest { public static void main(String[] args) { Comparator<String> c=new Comparator<String>(){ @Override public int compare(String o1, String o2) { return o1.compareTo(o2)*-1; //由大到小排序 } }; PriorityQueue<String> queue=new PriorityQueue<>(3, c); queue.offer("Mary"); queue.offer("Apple"); queue.offer("Thomas"); Iterator it=queue.iterator(); String s; while((s=queue.poll())!=null){ System.out.println(s); } } }
注意, String s=”abcd”;
s.compare(“defg”); 先一個字一個字比, 若前面比較小, 傳回負數, 前面比較大, 傳回正數, 二個都一樣, 傳回 0
List可以使用Iterator取得裏面的元素, 但每次 變動List之後,就必需重新取得Iterator, 否則會發生ConcurrentModificationException.
Map介面
Map在其他的語言如Python, C#中, 稱為字典, 是一個包含key及value的袋子. key必需是唯一, 值為物件. Map不繼承Collection介面. 使用put/putAll加入元素, get(key)取回元素
HashMap
無順序, 無排序, 可允許key及value都是null
LinkedHashMap
同HashMap, 但因為加了雙向鏈結, 所以走訪效能快, 新增刪除效能差
HashTable
同HashMap, 但這是thread-safe的, 鍵值不可為null
SortedMap
根據key值作自然排序, key不得重複也不可能null
TreeMap
實作SortedMap, 採二元樹排序, 裏面的鍵值必需是同一種資料型態
import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class MapTest { public static void main(String[] args) { System.out.println("--------HashMap-------"); Map<String, String> m1=new HashMap<>(); m1.put("Mercury", "水星"); m1.put("Venus", "金星"); m1.put("Earth", "地球"); m1.put("Mars", "火星"); m1.put("Jupiter", "木星"); Set<String> keys1=m1.keySet(); for(String key:keys1){ System.out.printf("%s=>%s\n", key,m1.get(key)); } System.out.println("--------LinkedHashMap-------"); Map<String, String> m2=new LinkedHashMap<>(); m2.put("Mercury", "水星"); m2.put("Venus", "金星"); m2.put("Earth", "地球"); m2.put("Mars", "火星"); m2.put("Jupiter", "木星"); m2.put("Saturn","土星"); m2.put("Uranus","天王星"); m2.put("Neptune","海王星"); m2.put("Pluto","冥王星"); Set<String> keys2=m2.keySet(); for(String key:keys2){ System.out.printf("%s=>%s\n", key,m2.get(key)); } System.out.println("--------TreeMap-------"); Map<String, String> m3=new TreeMap<>(); m3.put("Mercury", "水星"); m3.put("Venus", "金星"); m3.put("Earth", "地球"); m3.put("Mars", "火星"); m3.put("Jupiter", "木星"); Set<String> keys3=m3.keySet(); for(String key:keys3){ System.out.printf("%s=>%s\n", key,m3.get(key)); } } } 結果 : --------HashMap------- Earth=>地球 Mars=>火星 Jupiter=>木星 Venus=>金星 Mercury=>水星 --------LinkedHashMap------- Mercury=>水星 Venus=>金星 Earth=>地球 Mars=>火星 Jupiter=>木星 Saturn=>土星 Uranus=>天王星 Neptune=>海王星 Pluto=>冥王星 --------TreeMap------- Earth=>地球 Jupiter=>木星 Mars=>火星 Mercury=>水星 Venus=>金星
Deque 介面
Deque是Collection的子介面, 可同時作為queue及stack
queue : FIFO, add, remove
stack : LIFO, push, pop
Deque stack=new ArrayDeque<>(); stack.push("one"); stack.push("two");
Collections集合工具
Collections.synchronizedMap(map) : 變成thread-safe
Collections.sort(list, sort); 依sort方式來排序, 見下面說明
排序集合(Ordering Collections)
Comparable 介面 : 需實作compareTo(), 僅提供一個排序的方式
返回值需包含0, 1, -1三個, 加入Collection的物件, 會依此排序取出
Comparator介面 : 需實作compare(), 可建立多個排序方式
List<Student> list=new ArrayList<>();
Compartor<Student> sortName=new StudentSortName();
Comparator<Student>sortGpa=new StudentSortGpa();
Colloections.sort(list, sortName); <==依sortName排序
Colloections.sort(list, sortGpa); <==依sortGpa序
NavigableSet/NavigableMap
此二個會根據內容作自然排序
NavigableSet 為SortedSet, 只可用在TreeSet;
NavigableSet<Integer> ns1=new TreeSet<>();
NavigableMap為SortedMap, 只可用在TreeMap;
NavigableMap<Integer, Integer> ns2=new TreeMap<>();
並行集合
常見的有
ConcurrentHashMap : 同步HashMap實作
ConcurrentSkipListMap : 同步TreeMap實作
CopyOnWriteArrayList : 同步ArrayList實作