第十一章 持有对象
集合类:对象类型为List、Set、Queue和Map的类型。
11.1 泛型和类型安全的容器
使用预定义的泛型容器可以保存特定类型的对象,无论存取都不必关心类型转换。
该类型的子类对象也可以通过向上转型,存入到泛型容器中。
11.2 基本概念
Java容器类可以划分成两类:Collection与Map
1) Collection:一个独立元素的队列,这些元素都服从一条或多条规则。这一类又可以分为List、Set和Queue三个分类。
2) Map:一组成对的键值对对象,允许使用键查找值,也被称为字典或者关联数组。
通常情况下,在创建一个具体类对象时,会将其向上转型成为对应的接口,如:
List<Apple> apples = new ArrayList<>();
List<Apple> apples2 = new LinkedList<>();
但是如果需要使用到具体类的特有方法时,就无需向上转型,直接创建具体类对象即可。
Collection接口概括了序列的概念,而序列是一种存放一组对象的方式。该接口有add()方法,表示将一个新元素放置到Collection中。
11.3 添加一组元素
创建Collection对象的方式:
1) 直接创建实现Collection接口的具体类对象,如ArrayList等;
a) 可以不接受参数,然后使用for循环逐个元素添加,也可以使用addAll()方法,将一组元素直接添加;
b) 可以接受一个Collection对象作为参数,将该Collection对象的中所有元素存入新创建的Collection对象中。
2) 使用其他方法
a) 使用Arrays.asList(),该方法接受一个数组或者可变参数列表,并将之转换为List对象,需要注意的是,此种方式获得的List对象,由于底层实现仍然是数组,在添加或者删除元素时会出现UnsupportedOperationException异常。
b) 使用Collections.addAll(),这一方法可以接收一个Collection对象、一个数组或者是可变参数列表作为参数,得到新的Collection对象;
c) 使用Collection.addAll(),这一方法与上一种不同的是,它只能接受Collection对象作为参数,灵活性受到限制。
package com.mzm.chapter11;
import java.util.*;
/**
*
*/
public class AddingGroups {
public static void main(String[] args){
Collection<Integer> collection = new ArrayList<>(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);
//产生UnsupportedOperationException
//list.add(21);
}
}
之后的显示类型参数说明可能在之后的JDK版本中被修正了,没有书中说的运行结果。
10.4 容器的打印
Collection的toString()方法,以”[“开头,以”]”结尾,每个元素之间用”,”分隔;
Map的toString()方法,以”[“开头,以”]”结尾,每一对键值对之间,以”,”分隔,针对每一对键值对,键在前,值在后,中间使用”=”分隔。
10.5 List
addAll(int index, List<T> subList):在List的索引为index的位置,插入subList的所有元素;
contains(T t):判断某个对象是否在List中;
remove(T t):移除某个对象;
remove(int index):移除List中索引为index的元素;
removeAll():移除List中的全部元素;
indexOf(T t):获取某个对象在List中的索引;
add(int index, T t):在List中间插入元素,该操作消耗性能;
subList(int startIndex, int endIndex):获取子List,包头不包尾,本质是创建一个新的List;
containsAll(List<T> subList):List中是否包含subList中的全部元素;
retainAll(List<T> otherList):List与otherList的交集,注意该操作不创建新的List,而是在原来的List上,将不在otherList中的元素删除;
set(int index, T t):将List中索引为index的元素设置为t;
isEmpty():判断List长度是否为0;
clear():将List的所有元素清空;
toArray():将List转换为数组,注意是返回Object数组;
toArray(T list.get(i)):将List转换为数组,但返回的是类型与List的类型参数相同的数组;
11.6 迭代器
不考虑容器类型,对容器插入元素并将它们再次取回。
迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员无需关心该序列的底层实现。
迭代器也被称为轻量级对象,因为其创建代价小。
Java中的Iterator,它只能单向移动,具有如下方法:
iterator():返回一个Iterator对象,它将准备好返回序列的第一个元素;
next():返回序列的下一个元素;
hasNext():判断序列是否含有下一个元素;
remove():将迭代器新近返回的元素删除;
迭代器将遍历序列的操作与序列底层的结构分离,统一了对容器的访问方式。
11.6.1 ListIterator
ListIterator是Iterator的一个子类,只能用于各种List的访问。ListIterator可以双向移动,可以产生当前位置的前一个和后一个元素的索引,并且可以使用set()方法,将最近访问过的元素进行替换。此外,还可以通过listIterator(int index)的方法,获得一个一开始就指向index位置的ListIterator。
11.7 LinkedList
LinkedList的增删为O(1),对元素的随机访问为O(n);ArrayList的增删为O(n),对元素的随机访问为O(1)。
getFirst()/element()/peek():获取第一个元素,poll()在列表为空时返回null,另外两个抛出NoSuchElementException异常; removeFirst()/remove()/poll():移除并返回第一个元素,差异与上面类似;
addFirst()/add()/offer()/addLast():将某个元素添加的末尾;
removeLast():移除并返回最后一个元素。
11.8 Stack
栈是指先进后出的容器,也被称为叠加栈,最典型的例子是自助餐托盘。
可以使用LinkedList来实现:
package com.mzm.chapter11;
import java.util.LinkedList;
/**
*
*/
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<>();
public void push(T v){
storage.addFirst(v);
}
public T peek(){
return storage.getFirst();
}
public T pop(){
return storage.removeFirst();
}
public boolean empty(){
return storage.isEmpty();
}
public String toString(){
return storage.toString();
}
}
11.9 Set
Set不保存重复元素。
查找是Set最重要的操作,一般是使用HashSet。
HashSet、TreeSet和LinkedHashSet的区别在于,HashSet使用散列函数来维护元素的顺序,TreeSet是将元素存储在红黑树数据结构中,LinkedHashSet使用链表来维护元素的插入顺序。
方法包括:add()、addAll()、contains()、addAll()、containsAll()、remove()、removeAll()。
11.10 Map
Map是将对象映射到另一个对象。
11.11 Queue
队列是一个先进先出的容器。
LinkedList提供了方法以支持队列的行为,并且实现了Queue接口,所以可以使用Queue引用指向LinkedList对象。
11.11.1 PriorityQueue
队列规则:在给定一组队列中的元素的情况下,确定下一个弹出队列的元素的规则。
先进先出声明的是下一个元素应该是等待时间最长的元素。
优先级队列声明下一个弹出元素是最需要的元素(具有最高优先级)。
PriorityQueue在调用offer()方法插入元素时,默认是根据自然顺序排列的,可以传入自定义的Comparator来修改顺序。
package com.mzm.chapter11;
import java.util.*;
/**
* Created by 蒙卓明 on 2017/10/14.
*/
public class PriorityQueueDemo {
public static void main(String[] args){
//默认是升序排列
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
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<>(ints);
QueueDemo.printQ(priorityQueue);
//指定降序
priorityQueue = new PriorityQueue<>(ints.size(), 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<>(strings);
QueueDemo.printQ(stringPQ);
//指定降序
stringPQ = new PriorityQueue<>(strings.size(), Collections.reverseOrder());
stringPQ.addAll(strings);
QueueDemo.printQ(stringPQ);
Set<Character> charSet = new HashSet<>();
for(char c : fact.toCharArray()){
charSet.add(c);
}
PriorityQueue<Character> characterPQ = new PriorityQueue<>();
QueueDemo.printQ(characterPQ);
}
}
11.12 Collection和Iterator
如果要针对所有Collection类型提供共同的方法,最好是通过迭代器的方式。实现Collection接口,需要实现该接口的所有方法,较为麻烦,虽然AbstractCollection提供了Collection的默认实现,即使是继承该类,也必须提供迭代器的实现,何况已经继承了其他类的情况,所以最好是使用迭代器。
package com.mzm.chapter11;
import java.util.*;
/**
* Iterator比Collection要好一些
*
*/
public class InterfaceVsIterator {
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 display(Collection<Pet> pets){
for(Pet p : pets){
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void main(String[] args){
List<Pet> petList = Pet.arrayList(8);
Set<Pet> petSet = new HashSet<>(petList);
Map<String, Pet> petMap = new LinkedHashMap<>();
String[] names = {"Ralph", "Eric", "Robin", "Lacey", "Britney", "Sam", "Spot", "Fluffy"};
for(int i = 0; i < 8; i++){
petMap.put(names[i], petList.get(i));
}
display(petList);
display(petSet);
display(petList.iterator());
display(petSet.iterator());
System.out.println(petMap);
System.out.println(petMap.keySet());
display(petMap.values());
display(petMap.values().iterator());
}
}
package com.mzm.chapter11;
import java.util.AbstractCollection;
import java.util.Iterator;
/**
* AbstractCollection提供了Collection的默认实现,但即使继承该类,也同样要实现iterator()方法来提供迭代器
* 不如直接使用迭代器
*
*/
public class CollectionSequence extends AbstractCollection<Pet> {
private Pet[] pets = Pet.arrayList(8).toArray(new Pet[0]);
@Override
public Iterator<Pet> iterator() {
return new Iterator<Pet>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < pets.length;
}
@Override
public Pet next() {
return pets[index++];
}
public void remove(){
throw new UnsupportedOperationException();
}
};
}
@Override
public int size() {
return pets.length;
}
public static void main(String[] args){
CollectionSequence c = new CollectionSequence();
InterfaceVsIterator.display(c);
InterfaceVsIterator.display(c.iterator());
}
}
package com.mzm.chapter11;
import java.util.Iterator;
/**
* 当这个类继承了其他类时,不能再继承AbstractCollection了
*
*/
public class NonCollectionSequence extends PetSequence{
public Iterator<Pet> iterator(){
return new Iterator<Pet>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < pets.length;
}
@Override
public Pet next() {
return pets[index++];
}
public void remove(){
throw new UnsupportedOperationException();
}
};
}
}
class PetSequence{
protected Pet[] pets = Pet.arrayList(8).toArray(new Pet[0]);
}
11.13 Foreach与迭代器
所有的Collection都能使用foreach语法遍历,因为其实现了Iterable接口,该接口包含一个能够产生Iterator的iterator()的方法。
注意数组没有实现Iterable接口。
11.13.1 适配器方法惯用法
如果需要在foreach中进行反序遍历,可以使用适配器设计模式。
package com.mzm.chapter11;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
/**
* 使用适配器设计模式在foreach中添加反向遍历
*
*/
public class AdapterMethodIdiom {
public static void main(String[] args){
ReversibleArrayList<String> ral =
new ReversibleArrayList<>(Arrays.asList("To be or not to be".split(" ")));
//正向遍历
for(String s : ral){
System.out.print(s + " ");
}
System.out.println();
//反向遍历
for(String s : ral.reverse()){
System.out.print(s + " ");
}
}
}
class ReversibleArrayList<T> extends ArrayList<T>{
public ReversibleArrayList(Collection<T> c){
super(c);
}
public Iterable<T> reverse(){
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
int current = size() - 1;
@Override
public boolean hasNext() {
return current > -1;
}
@Override
public T next() {
return get(current--);
}
public void remove(){
throw new UnsupportedOperationException();
}
};
}
};
}
}