11.1 泛型和安全的容器
可以把ArrayList当做“可以自动扩充自身尺寸的数组”来看待。使用ArrayList相当简单。创建一个实例,用add()来插入对象,然后用get()
来访问这些对象,此时需要使用索引,就像数组一样,需要方括号。ArrayList还有一个size()方法,使你可以知道有多少元素添加了进来。
//: holding/ApplesAndOrangesWithGenerics.java
import java.util.*;
public class ApplesAndOrangesWithGenerics {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<Apple>();
for(int i = 0; i < 3; i++)
apples.add(new Apple());
// Compile-time error:
// apples.add(new Orange());
for(int i = 0; i < apples.size(); i++)
System.out.println(apples.get(i).id());
// Using foreach:
for(Apple c : apples)
System.out.println(c.id());
}
} /* Output:
0
1
2
0
1
2
*///:~
当ArrayList没有添加任何参数时,你可以添加任意对象进入其中,而不会报错,但是取出对象时,你得到的只是Object对象,必须将其转型为Apple,通过使用泛型,就可以在编译期防止将错误的类型的对象错误的放到容器中。
11.2 基本概念
1)Collection 。一个独立元素的序列。List必须按照插入的顺序保存元素,而Set不能有重复的元素。Queue按照排队的顺序来确定对象产生的顺序(通常与他被插入的对象的顺序相同)
2)Map 一组成对的“键值对”对象,允许你使用键来查找值,ArrayList允许你使用数字来查找值,因此在某种意义上来讲,它将数字与对象关联在了一起,你可以使用键对象来查找对象,就像在字典中使用单词来定义一样,Map是强大的编程工具。
//: holding/SimpleCollection.java
import java.util.*;
public class SimpleCollection {
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<Integer>();
for(int i = 0; i < 10; i++)
c.add(i); // Autoboxing
for(Integer i : c)
System.out.print(i + ", ");
}
} /* Output:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
*///:~
11.3 添加一组元素
ArrayList()方法接受一个数组或四一个用逗号分隔·的元素列表(使用可变参数),并将其转换为一个List对象,Collection.addAll()接受一个Collection对象,以及一个数组活用逗号分隔的列表,并将元素添加到Collection中,下面的事例展示了这两个方法,以及更加传统的addAll方法。
//: holding/AddingGroups.java
// Adding groups of elements to Collection objects.
import java.util.*;
public class AddingGroups {
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));
// Runs significantly faster, but you can't
// construct a Collection this way:
Collections.addAll(collection, 11, 12, 13, 14, 15);
Collections.addAll(collection, moreInts);
// Produces a list "backed by" an array:
List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
list.set(1, 99); // OK -- modify an element
// list.add(21); // Runtime error because the
// underlying array cannot be resized.
}
} ///:~
调用Collection.addAll这种方式很方便,因此它是首选方式。
11.4 容器的打印
必须使用ArrayList.toString()来产生数组的课打印表示。
//: holding/PrintingContainers.java
// Containers print themselves automatically.
import java.util.*;
import static net.mindview.util.Print.*;
public class PrintingContainers {
static Collection fill(Collection<String> collection) {
collection.add("rat");
collection.add("cat");
collection.add("dog");
collection.add("dog");
return collection;
}
static Map fill(Map<String,String> map) {
map.put("rat", "Fuzzy");
map.put("cat", "Rags");
map.put("dog", "Bosco");
map.put("dog", "Spot");
return map;
}
public static void main(String[] args) {
print(fill(new ArrayList<String>()));
print(fill(new LinkedList<String>()));
print(fill(new HashSet<String>()));
print(fill(new TreeSet<String>()));
print(fill(new LinkedHashSet<String>()));
print(fill(new HashMap<String,String>()));
print(fill(new TreeMap<String,String>()));
print(fill(new LinkedHashMap<String,String>()));
}
} /* Output:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[dog, cat, rat]
[cat, dog, rat]
[rat, cat, dog]
{dog=Spot, cat=Rags, rat=Fuzzy}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}
*///:~
ArrayList和LinkedList都是List类型,他们都按照顺序来保存对象,两者不同之处不仅在于执行某些类型操作时的性能,而且LinkedList包含的操作要多于ArrayList。
HashSet,TreeSet,LinkedHashSet都是Set类型,HashSet是最快的获取元素的方式,如果顺序很重要,使用TreeSet,他使用的按照比较结果的升序保存对象,LinkedHashSet按照添加的顺序来保存对象。
11.5 List
下面的例子介绍了一些List的操作。
//: holding/ListFeatures.java
import typeinfo.pets.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class ListFeatures {
public static void main(String[] args) {
Random rand = new Random(47);
List<Pet> pets = Pets.arrayList(7);
print("1: " + pets);
Hamster h = new Hamster();
pets.add(h); // Automatically resizes
print("2: " + pets);
print("3: " + pets.contains(h));
pets.remove(h); // Remove by object
Pet p = pets.get(2);
print("4: " + p + " " + pets.indexOf(p));
Pet cymric = new Cymric();
print("5: " + pets.indexOf(cymric));
print("6: " + pets.remove(cymric));
// Must be the exact object:
print("7: " + pets.remove(p));
print("8: " + pets);
pets.add(3, new Mouse()); // Insert at an index
print("9: " + pets);
List<Pet> sub = pets.subList(1, 4);
print("subList: " + sub);
print("10: " + pets.containsAll(sub));
Collections.sort(sub); // In-place sort
print("sorted subList: " + sub);
// Order is not important in containsAll():
print("11: " + pets.containsAll(sub));
Collections.shuffle(sub, rand); // Mix it up
print("shuffled subList: " + sub);
print("12: " + pets.containsAll(sub));
List<Pet> copy = new ArrayList<Pet>(pets);
sub = Arrays.asList(pets.get(1), pets.get(4));
print("sub: " + sub);
copy.retainAll(sub);
print("13: " + copy);
copy = new ArrayList<Pet>(pets); // Get a fresh copy
copy.remove(2); // Remove by index
print("14: " + copy);
copy.removeAll(sub); // Only removes exact objects
print("15: " + copy);
copy.set(1, new Mouse()); // Replace an element
print("16: " + copy);
copy.addAll(2, sub); // Insert a list in the middle
print("17: " + copy);
print("18: " + pets.isEmpty());
pets.clear(); // Remove all elements
print("19: " + pets);
print("20: " + pets.isEmpty());
pets.addAll(Pets.arrayList(4));
print("21: " + pets);
Object[] o = pets.toArray();
print("22: " + o[3]);
Pet[] pa = pets.toArray(new Pet[0]);
print("23: " + pa[3].id());
}
} /* Output:
1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]
2: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]
3: true
4: Cymric 2
5: -1
6: false
7: true
8: [Rat, Manx, Mutt, Pug, Cymric, Pug]
9: [Rat, Manx, Mutt, Mouse, Pug, Cymric, Pug]
subList: [Manx, Mutt, Mouse]
10: true
sorted subList: [Manx, Mouse, Mutt]
11: true
shuffled subList: [Mouse, Manx, Mutt]
12: true
sub: [Mouse, Pug]
13: [Mouse, Pug]
14: [Rat, Mouse, Mutt, Pug, Cymric, Pug]
15: [Rat, Mutt, Cymric, Pug]
16: [Rat, Mouse, Cymric, Pug]
17: [Rat, Mouse, Mouse, Pug, Cymric, Pug]
18: false
19: []
20: true
21: [Manx, Cymric, Rat, EgyptianMau]
22: EgyptianMau
23: 14
*///:~
你可以用contains()方法来确定某个对象是否在列表中,如果你想移除一个对象,则可以将这个对象的引用传递给remove()方法。 同样如果你有一个对象的引用,则可以使用indexOf()来发现该对象在List中所处位置的索引编号。
subList()方法允许你很容易的从较大的列表中创建出一个片段,而将这个结果传递个较大的列表的containsAll(),很自然的会得到true。
retainAll()是一种有效的交集操作,他保留了同时在copy中与sub中的元素。
可以使用toArray()方法将任意的Collection转换为一个数组,如果参数数组太小,存放不下List中的所有元素,toArray()将创建一个具有合适尺寸的数组。
11.6 迭代器
迭代器是一个对象,它的工作是遍历并选择序列中的对象。而客户端程序员不必关心该序列的底层结构,创建迭代器的代价很小,因此它也被称为轻量级对象,java的Iterator只能单向移动,这个Iterator只能用来。
1)使用方法Iterator()要求容器返回一个Iterator。Iterator将准备好返回第一个元素。
2)使用next()获得序列中的下一个对象。
3)使用hasNext()检查序列中是否还有元素。
4)使用remove()将迭代器新返回的元素删除。
//: holding/SimpleIteration.java
import typeinfo.pets.*;
import java.util.*;
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(12);
Iterator<Pet> it = pets.iterator();
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
// A simpler approach, when possible:
for(Pet p : pets)
System.out.print(p.id() + ":" + p + " ");
System.out.println();
// An Iterator can also remove elements:
it = pets.iterator();
for(int i = 0; i < 6; i++) {
it.next();
it.remove();
}
System.out.println(pets);
}
} /* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster
[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]
*///:~
迭代器能够将遍历序列的操作与序列底层的结构分离,正由如此,我有时会说:迭代器统一了对容器的访问的方式。
//: holding/CrossContainerIteration.java
import typeinfo.pets.*;
import java.util.*;
public class CrossContainerIteration {
public static void display(Iterator<Pet> it) {
while(it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void main(String[] args) {
ArrayList<Pet> pets = Pets.arrayList(8);
LinkedList<Pet> petsLL = new LinkedList<Pet>(pets);
HashSet<Pet> petsHS = new HashSet<Pet>(pets);
TreeSet<Pet> petsTS = new TreeSet<Pet>(pets);
display(pets.iterator());
display(petsLL.iterator());
display(petsHS.iterator());
display(petsTS.iterator());
}
} /* Output:
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
4:Pug 6:Pug 3:Mutt 1:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat
5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat
*///:~
11.6.1 ListIterator
ListIterator是一个更加强大的Iterator的子类型,它只能用于各种List类的访问,但是ListIterator可以双向移动,你可以使用ListIterator(n)创建一个一开始索引为n的元素出的ListIterator。n指的是第几个元素,而不是元素的地址。
你也可以用迭代器来进行add(),set(),remove()等操作
//: holding/ListIteration.java
import typeinfo.pets.*;
import java.util.*;
public class ListIteration {
public static void main(String[] args) {
List<Pet> pets = Pets.arrayList(8);
ListIterator<Pet> it = pets.listIterator();
while(it.hasNext())
System.out.print(it.next() + ", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
System.out.println();
// Backwards:
while(it.hasPrevious())
System.out.print(it.previous().id() + " ");
System.out.println();
System.out.println(pets);
it = pets.listIterator(3);
while(it.hasNext()) {
it.next();
it.set(Pets.randomPet());
}
System.out.println(pets);
}
} /* Output:
Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;
7 6 5 4 3 2 1 0
[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]
[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]
*///:~
11.7 LinkedList
getFirst()和element()完全一样,他们都返回列表的头部,而不移除他,如果List为空,则抛出NoSuchElementException错误。peek()方法与这两个方式只是稍有差异,返回一个null。
remove()与removeFirst()也是完全一样的,他们移除这个表的头,而在表为空的时候,则抛出NoSuchElementException错误,poll()在列表为空的时候返回null。
addFirst()与add()和addLast()相同,都是在表中插入元素。
removeLast()移除并返回列表的最后一个元素。
//: holding/LinkedListFeatures.java
import typeinfo.pets.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<Pet> pets =
new LinkedList<Pet>(Pets.arrayList(5));
print(pets);
// Identical:
print("pets.getFirst(): " + pets.getFirst());
print("pets.element(): " + pets.element());
// Only differs in empty-list behavior:
print("pets.peek(): " + pets.peek());
// Identical; remove and return the first element:
print("pets.remove(): " + pets.remove());
print("pets.removeFirst(): " + pets.removeFirst());
// Only differs in empty-list behavior:
print("pets.poll(): " + pets.poll());
print(pets);
pets.addFirst(new Rat());
print("After addFirst(): " + pets);
pets.offer(Pets.randomPet());
print("After offer(): " + pets);
pets.add(Pets.randomPet());
print("After add(): " + pets);
pets.addLast(new Hamster());
print("After addLast(): " + pets);
print("pets.removeLast(): " + pets.removeLast());
}
} /* Output:
[Rat, Manx, Cymric, Mutt, Pug]
pets.getFirst(): Rat
pets.element(): Rat
pets.peek(): Rat
pets.remove(): Rat
pets.removeFirst(): Manx
pets.poll(): Cymric
[Mutt, Pug]
After addFirst(): [Rat, Mutt, Pug]
After offer(): [Rat, Mutt, Pug, Cymric]
After add(): [Rat, Mutt, Pug, Cymric, Pug]
After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]
pets.removeLast(): Hamster
*///:~
11.9 Set
Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面两个不同的Set,实际上Set就是Collection,只是行为不同。
Set不保存重复的元素,Set中最常用的是测试归属性,你可以很容易的查询到某个对象是否在某个Set中,因此,查找就成了Set中最重要的操作,所以通常都会选择一个HashSet的实现,它专门对快速查找进行了优化。
//: holding/SetOfInteger.java
import java.util.*;
public class SetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> intset = new HashSet<Integer>();
for(int i = 0; i < 10000; i++)
intset.add(rand.nextInt(30));
System.out.println(intset);
}
} /* Output:
[15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 29, 14, 24, 4, 19, 26, 11, 18, 3, 12, 27, 17, 2, 13, 28, 20, 25, 10, 5, 0]
*///:~
你可以注意到,输出的顺序没有任何规律可言,HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为他们的实现具有不同的元素存储方式。如果想要对结果进行排序,一种方式是用TreeSet来代替HashSet:
//: holding/SortedSetOfInteger.java
import java.util.*;
public class SortedSetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
SortedSet<Integer> intset = new TreeSet<Integer>();
for(int i = 0; i < 10000; i++)
intset.add(rand.nextInt(30));
System.out.println(intset);
}
} /* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
*///:~
11.10 Map
将对象映射到其他对象的能力是一种解决编程问题的杀手锏,例如要计算Random产生的各种不同范围的数字进行计数,Map可以很容易的解决问题。在本例中,键是由Random产生的数字,而值是该数字出现的次数;
//: holding/Statistics.java
// Simple demonstration of HashMap.
import java.util.*;
public class Statistics {
public static void main(String[] args) {
Random rand = new Random(47);
Map<Integer,Integer> m =
new HashMap<Integer,Integer>();
for(int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
int r = rand.nextInt(20);
Integer freq = m.get(r);
m.put(r, freq == null ? 1 : freq + 1);
}
System.out.println(m);
}
} /* Output:
{15=497, 4=481, 19=464, 8=468, 11=531, 16=533, 18=478, 3=508, 7=471, 12=521, 17=509, 2=489, 13=506, 9=549, 6=519, 1=502, 14=477, 10=513, 5=503, 0=481}
*///:~
下面的事例允许你使用一个String描述来查找Pet,它还展示了你可以使用怎样的方法使用containsKey(),和containValue()来测试一个Map(),以便查看他是否包含某个键或者某个类。
//: holding/PetMap.java
import typeinfo.pets.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class PetMap {
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("Ginger"));
petMap.put("My Hamster", new Hamster("Bosco"));
print(petMap);
Pet dog = petMap.get("My Dog");
print(dog);
print(petMap.containsKey("My Dog"));
print(petMap.containsValue(dog));
}
} /* Output:
{My Cat=Cat Molly, My Hamster=Hamster Bosco, My Dog=Dog Ginger}
Dog Ginger
true
true
*///:~
Map和其他Collection一样,可以扩展,Map的值可以是其他容器,使得我们可以很轻松生成强大的数据结构。
例如: Map<Person , List>;
11.11 Queue
LinkList提供了方法以支持队列的对象,并且它实现了Queue接口,因此LinkList可以作为Queue的一种实现,通过将LinkList向上转型为Queue。
//: holding/QueueDemo.java
// Upcasting to a Queue from a LinkedList.
import java.util.*;
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) {
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);
printQ(qc);
}
} /* Output:
8 1 1 1 5 14 3 1 0 1
B r o n t o s a u r u s
*///:~
offer()方法是与Queue相关的方法之一,它在允许的情况下将一个元素插入队尾,或者返回false,peek()和element()都将在不移除的情况下返回队头,但是peek()在队列为空的时候返回null,而element()会抛出NoSuchElementException异常。poll()和remove()方法将移除并返回队头,但是poll()在队列为空时候返回null,而remove()会抛出NoSuchElementException()异常
Queue接口窄化了对LinkList的方法的访问权限,以使得只有恰当的方法才可以使用,因此,你能够访问的LinkList方法会变少。
11.11.1 PriorityQueue
当你在PriorityQueue上调用一个offer来插入一个对象是,这个对象会在队列中被排序,默认的排序将使用对象在队列中的自然顺序,但是你可以使用Comparator来修改这个顺序,PriorityQueue可以确保你在调用peek()poll()remove()方法时,获取的元素将是队列中优先级最高的元素。
//: holding/PriorityQueueDemo.java
import java.util.*;
public class PriorityQueueDemo {
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));
QueueDemo.printQ(priorityQueue);
List<Integer> ints = Arrays.asList(25, 22, 20,
18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);
priorityQueue = new PriorityQueue<Integer>(ints);
QueueDemo.printQ(priorityQueue);
priorityQueue = new PriorityQueue<Integer>(
Collections.reverseOrder());
priorityQueue.addAll(ints);
QueueDemo.printQ(priorityQueue);
String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION";
List<String> strings = Arrays.asList(fact.split(""));
PriorityQueue<String> stringPQ =
new PriorityQueue<String>(strings);
QueueDemo.printQ(stringPQ);
stringPQ = new PriorityQueue<String>(
strings.size(), Collections.reverseOrder());
stringPQ.addAll(strings);
QueueDemo.printQ(stringPQ);
Set<Character> charSet = new HashSet<Character>();
for(char c : fact.toCharArray())
charSet.add(c); // Autoboxing
PriorityQueue<Character> characterPQ =
new PriorityQueue<Character>(charSet);
QueueDemo.printQ(characterPQ);
}
} /* Output:
0 1 1 1 1 1 3 5 8 14
1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25
25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1
A A B C C C D D E E E F H H I I L N N O O O O S S S T T U U U W
W U U U T T S S S O O O O N N L I I H H F E E E D D C C C B A A
A B C D E F H I L N O S T U W
*///:~
11.13 Foreach与迭代器
能够与Foreach工作是所有Collection对象的特性。之所以能够工作,是因为java SE5中加入了新的被称为Iterator的接口,该接口包含了一个能够产生Iterator的iterator()的方法,并且Iterator接口被Foreach用来在序列中移动,因此如果你创建了任何实现ITerator的类,都可以将它用于Foreach语句中:
//: holding/IterableClass.java
// Anything Iterable works with foreach.
import java.util.*;
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how " +
"we know the Earth to be banana-shaped.").split(" ");
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
public boolean hasNext() {
return index < words.length;
}
public String next() { return words[index++]; }
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for(String s : new IterableClass())
System.out.print(s + " ");
}
} /* Output:
And that is how we know the Earth to be banana-shaped.
*///:~