首先给出一个整体类图结构:
常用的容器用黑色粗线框表示,点线框表示接口,实线框表示普通的类,带有空心箭头的点线表示一个特定的类实现了一个接口,实心箭头表示某个类可以生成箭头所指向类的对象。
添加一组元素
在java.util包中的Array和Collection类中都有很实用方法,Arrays.asList()方法接受一个数组或者是一个用逗号分隔的元素列表,并将其转换为一个List对象。Collections.addAll()方法接受一个Collection对象,数组,逗号分隔的列表。
public static void main(String[] args) {
Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
Integer[] moreInts = {6,7,8,9,10};
collection.addAll(Arrays.asList(moreInts));
Collections.addAll(collection, 11,12,13,14,15);
Collections.addAll(collection, moreInts);
List<Integer> list = Arrays.asList(16,17,18,19,20);
list.set(1, 99);
}
你也可以直接使用Arrays.asList的输出,将其当做List,但是在这种情况下,其底层表示的是数组,因此不能调整大小,如果add或者delete就有可能改变数组大小,此时会得到运行时错误(Unsupported Operation)。
List<Snow> snow1 = Arrays.asList(new Crusty(), new Slush(), new Powder());
//light heavy都集成powder powder集成snow
//wont compile
//List<Snow> snow2 Arrays.asList(new Light(), new Heavy());
//Compiler says:
//found: java.util.List<Poder>
//required: java.util.List<Snow>
List<Snow> snow3 = new ArrayList<Snow>();
Collections.addAll(snow3, new Lisht(), new Heavy());
List<Snow> snow4 = Arrays.<Snow>asList(new Lisht(), new Heavy());
当试图创建snow2时,Arrays.asList中只有Powder类型,因此它会创建List<Powder>而不是List<Snow>,尽管Collections.addAll工作的很好,因为它从第一个参数中了解到了目标类型是什么。
正如在snow4的操作中所看到的,可以在Arrays.asList中间插入一条“线索”,以告诉编译器对于由Arrays.asList产生的List类型,事迹的目标类型应该是什么。这称为现实类型参数说明。
容器的打印
Collection在每个槽中只能保存一个元素。此类容器包括:List,它以特定的顺序保存一组元素;Set。元素不能重复;Queue,只允许在容器的一“端”插入对象,并从另外一端移除对象。Map则用大括号扩住(打印形式)。
ArrayList和LinkedList都是List类型。它们都是按照插入的顺序保存元素。
HashSet、TreeSet和LinkedHashSet都是Set类型,HashSet是最快的获取元素的方式,如果存储顺序很重要,那么可以使用TreeSet,它按照比较结果的升序保存对象;或者使用LinkedHashSet,它按照被添加的顺序保存对象。
Map:HashMap/TreeMap和LinkedHashMap。与HashSet一样,Hash
List
List接口在Collection的基础上添加了大量的方法,使得可以在lIst的中间插入和移动元素。
有两种类型的List:
基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移动元素时较慢。
LinkedList,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LickedList在随机访问方面相对较慢,但是他的特性集较ArrayList更大。
如果你有一个对象的引用,则可以使用indexOf()来发现该对象在List中所在位置的索引编号。
它们有很多方法,具体看api文档。
迭代器
Java的iterator只能单向移动,这个Iterator只能用来:
(1) 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
(2) 使用next()获得序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素。
(4) 使用remove()将迭代器新近返回的元素删除。
有了Iterator就不必为容器中元素的数量操心了,那是由hasNext()和next()关心的事情。
如果你只是向前遍历List,并不打算修改List对象本身,那么你可以看到foreach语法会显得更加简洁。
Iterator还可以移除由next()产生的最后一个元素,这意味着在调用remove()之前必须先调用next()。
public class Test {
public static void main(String[] args) {
LinkedList<Pet> petsLL new LinkedList<Pet>(pets);
HashSet<Pet> petsHS = new HashSet<Pet>(pets);
display(pets.iterator());
display(petsLL.iterator());
display(petsHS.iterator());
}
public static void display(Iterator<Pet> it) {
while(it.hasNext()) {
Pet p = it.next();
System.out.println(p.getId());
}
}
}
ListIterator
ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问。尽管Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素。listIterator()开始指向List的开始处,listIterator(n)指向列表索引为n的元素。
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(8);
ListIterator<Pet> it = pets.listIterator();
while(it.hasNext()) {
System.out.println(it.next() + it.nextIndex() + it.previousIndex());
}
while(it.hasPrevious()) {
System.out.println(it.previous().getId());
}
it = pets.listIterator(3);
while(it.hasNext()) {
it.next();
it.set(Pets.randomPet());
}
}
LinkedList
LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更高效,但是在随机访问就稍逊色一些。
浏览Queue接口就会发现,它在LinkedList的基础上添加了element()、offer() peek() poll()和remove()方法,以使其可以成为一个Queue的实现。
Stack
“栈”通常是指“后进先出”的容器。
LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈使用,不过,有时一个真正的“栈”更能把事情讲清楚:
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push(T t) {
storage.addFirst(t);
}
public T peek() {
return storage.getFirst();
}
public T pop() {
return storage.removeFirst();
}
public boolean empty() {
return storage.isEmpty();
}
public String toString() {
return storage.toString();
}
}
尽管已经有了java.util.Stack,但是LinkedList可以产生更好的Stack,因此LinkedList所采用的方式更是可取的。
Set
Set不保存重复的元素。查询时Set中最重要的操作,因此你通常都会选择一个HashSet的实现,它专门对快速查找进行优化。
HashSet使用了散列。HashSet所维护的循序和TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式。TreeSet将元素存储在红黑树数据结构中,而HashSet使用的是散列函数。LinkedHashList因为查询速度的原因也使用了散列,但是看起来他使用了链表来维护元素的插入顺序。
如果你想对结果排序,一种方式是使用TreeSet来代替HashSet
Map
将对象映射到其他对象的能力是一种解决变成问题的杀手锏。
下面允许你使用一个String描述来查找Pet,它还展示了你可以使用怎样的方法通过使用ContainsKey()和containsValue()来测试一个Map:
public static void main(String[] args) {
Map<String, Pet> petMap = new HashMap<String, Pet>();
petMap.put("My cat", new Cat("Molly"));
petMap.put("My dog", new Dog("dog"));
petMap.put("My Hamster", new Hamster("Hamster"));
System.out.print{petMap);
System.out.println(petMap.containsKey("My dog"));
System.out.println(petMap.containsValue(dog));
}
Queue
队列是一个典型的先进先出的容器。
LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue,下面是示例使用了在Queue接口中与Queue相关的方法:
public class QueueDemo {
public static void printQ(Queue queue) {
while(queue.peek() != null) {
System.out.println(queue.remove());
}
}
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for(int i=0; i<10; i++) {
queue.offer(rand.nextInt(i+10));
}
printQ(queue);
Queue<Character> qc = new LinkedList<Character>();
for(char c : "Brontosaurus".toCharArray()) {
qc.offer(c);
}
}
}
PriorityQueue
优先级队列声明下一个弹出元素是最需要的元素。
当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是你可以通过提供自己的Camparator来修改这个顺序。PriorityQueue可以确保当你调用peek poll remove方法时,获得元素将是队列中优先级最高的元素。
public class QueueDemo {
public static void printQ(Queue queue) {
while(queue.peek() != null) {
System.out.print(queue.remove() + " ");
}
System.out.println();
}
public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>();
Random rand = new Random(47);
for(int i=0; i<10; i++) {
priorityQueue.offer(rand.nextInt(i+10));
}
printQ(priorityQueue);
List<Integer> ints = Arrays.asList(25,22,12,51,124,564,23,64,32,64,1,2,3,99,77,66);
priorityQueue = new PriorityQueue<Integer>(ints);
printQ(priorityQueue);
priorityQueue = new PriorityQueue<Integer>(ints.size(), Collections.reverseOrder());
priorityQueue.addAll(ints);
printQ(priorityQueue);
}
}
Collections.reverseOrder产生的反序Comparator。
Collection和Iterator
Collection是描述所有序列容器的共性根接口。另外java.util.AbstractCollection类提供了Collection的默认实现,使得你可以创建AbstractCollection子类型而不必重复代码。
生成Iterator是将队列与消费队列的方法连接在一起耦合度最小的方式,并且与实现Collection相比,它在序列类上所施加的约束也少得多。
Foreach与迭代器
由于cs是一个Collection,所以这段代码展示了能够与foreach一起工作是所有Collection对象的特性。
之所以能够工作,是因为java SE5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator方法,并且Iterator接口被foreach用来在序列中移动。因此如果你创建了任何实现了Iterable的类,都可以将它用于foreach语句中:
总结
如果要进行大量的随机访问,就使用ArrayList,如果要经常从表中间插入或删除元素,则应该使用LinkedList。
各种Queue以及栈的行为,由LinkedList支持。
Map是一种将对象与对象相关联的设计。HashMao设计用来快速访问;而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
新程序中不应该使用过时的Vector Hashtable和Stack。