.#完整的容器分类法
1.填充容器
1.Collections类也有一些实用的用于填充的方法,其中包括fill()。与Arrays的一样,也只是复制同一个对象引用来填充整个容器,并且只对List有用。
一.使用Abstract类
1.对于产生用于容器的测试数据问题,有一种方式是创建定制的Collection和Map实现。每个java.util容器都有自己的Abstract类,它们提供了该容器的部分实现,因此你必须做的只是去实现那些产生想要容器所必需的方法。
Collection的功能方法
1.下面是可以通过Collection执行的所有操作:
Method | Description |
---|---|
boolean add(T) | 确保容器持有具有泛型类型T的参数。如果没有将此参数添加进容器,则返回false |
boolean addAll(Co�llection extends T>) | 添加参数中的所有元素。只要添加了任意元素就返回true |
void clear() | 移除容器中的所有元素 |
boolean contains(T) | 如果容器已持有具有泛型类型T的词参数,则返回true |
boolean containsAll(Collection>) | 如果容器持有此参数中的所有元素,则返回true |
boolean isEmpty() | 容器中没有元素时返回true |
Iterator |
返回一个Iterator |
boolean remove(Object) | 如果参数在容器中,则移除此元素的一个实例。如果做了移除动作,则返回true |
boolean removeAll(Collection>) | 移除参数中的所有元素。只要有移除动作发生就返回true |
boolean retainAll(Collection>) | 只保存参数中的元素(即“交集”)。只要Collection发生改变就返回true |
int size() | 返回容器中的元素的数目 |
Object[] toArray() | 返回一个数组,该数组包含容器中的所有元素 |
返回一个数组,该数组包含容器中的所有元素。返回结果的运行时类型与参数数组a的类型相同,而不是单纯的Object |
2.由于Collection包括Set,因此,如果想检查Collection中的元素,那就必须使用迭代器。
可选操作
1.执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义。
2.将方法定义为可选是因为这样做可以防止在设计中出现接口爆炸的情况。
3.“未获支持的操作”这种方式可以实现Java容器类库的一个重要目标:容器应该易学易用。未获支持的操作是一种特例,可以延迟到需要时再实现。但是为了让这种方式工作:
(1)UnsupportedOperationException必须是一种罕见事件。即,对大多数类来说,所有操作都应该可以工作,只有在特例中才会有未获支持操作。这种设计意味着,在Java容器类库中,如果想要创建新的Collection,但是没有为Collection接口中的所有方法都提供有意义的定义,那么它仍旧适合现有的类库。
(2)如果一个操作是未支持的,那么在实现接口的时候就会导致UnsupportedOperationException异常。
一.未获支持操作
1.最常见的未获支持操作,都来源于背后由固定尺寸的数据结构支持的容器。
public class Unsupported {
public static void test(String msg, List list) {
Collection c = list;
Collection subList = list.subList(1, 8);
Collection c2 = new ArrayList(subList);
System.out.println("---" + msg + "---");
try {
c.retainAll(c2);
} catch (Exception e) {
System.out.println("retainAll():" + e);
}
try {
c.removeAll(c2);
} catch (Exception e) {
System.out.println("removeAll():" + e);
}
try {
c.clear();
} catch (Exception e) {
System.out.println("clear():" + e);
}
try {
c.add("X");
} catch (Exception e) {
System.out.println("add():" + e);
}
try {
c.addAll(c2);
} catch (Exception e) {
System.out.println("addAll():" + e);
}
try {
c.remove("C");
} catch (Exception e) {
System.out.println("remove():" + e);
}
try {
list.set(0, "X");
} catch (Exception e) {
System.out.println("List.set():" + e);
}
}
public static void main(String...args) {
List list = Arrays.asList(("A B C D E F").split(" "));
test("Arrays.asList()", list);
test("unmodifiableList()", Collection.unmodifiableList(new ArrayList(list)));
}
}
/* Output:
---Arrays.asList()---
retainAll():java.lang.UnsupportedOperationException
removeAll():java.lang.UnsupportedOperationException
clear():java.lang.UnsupportedOperationException
add():java.lang.UnsupportedOperationException
addAll():java.lang.UnsupportedOperationException
remove():java.lang.UnsupportedOperationException
---unmodifiableList()---
retainAll():java.lang.UnsupportedOperationException
removeAll():java.lang.UnsupportedOperationException
clear():java.lang.UnsupportedOperationException
add():java.lang.UnsupportedOperationException
addAll():java.lang.UnsupportedOperationException
remove():java.lang.UnsupportedOperationException
List.set():java.lang.UnsupportedOperationException
*/
上面的代码中,Arrays.asList()会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作,因此除了set()方法,其它方法都会抛出异常。Collections.unmodifiableList()产生的是一个不可修改的列表,因此任意方法都不能对它产生修改。
List的功能方法
1.基本的List很容易使用:大多数的时候只是调用add()添加对象,使用get()一次取出一个元素,以及调用iterator()获取用于该序列的Iterator。
Set和存储顺序
1.在Java中有诸如Integer和String这样的预定义类型,这些类型被定义为可依在容器内部使用。当创建自己的类型时,要意识到Set需要一种方式来维护存储顺序,而存储顺序如何维护,则是在Set的不同实现之间会有所变化。
2.不同Set实现不仅具有不同当行为,而且它们对于放置在特定的Set中的元素类型也有不同的要求:
Set类型 | 要求 |
---|---|
Set | 存入Set的每个元素都必须是唯一的,因为Set不重复保存元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序 |
HashSet | 为快速查找而设计的Set。存入HashSet的元素必须定义HashCode() |
TreeSet | 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须事项Comparable接口 |
LinkedHashSet | 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的顺序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示,元素也必须定义hashCode()方法 |
3.如果没有其他限制,HashSet就应该是你默认的选择,因为它对速度进行了优化。
4.通常你必须为散列存储和树型存储都创建一个equals()方法,但是hashCode()只有在这个类将会被置于HashSet或者LinkedHashSet中市=时才是必需的。你应该在覆盖equals()方法的同时覆盖hashCode()方法,定义hashCode()的机制将会在后面介绍:
class SetType {
int i;
public SetType(int n) {
i = n;
}
public boolean equals(Object o) {
return o instanceof SetType && (i == (SetType) o).i;
}
public String toString() {
return Integer.toString(i);
}
}
class HashType extends SetType {
public HashType(int n) {
super(n);
}
public int hashCode() {
return i;
}
}
class TreeType extends SetType implements Comparable {
public TreeType(int n) {
super(n);
}
public compareTo(TreeType arg) {
return (arg.i < i ? -1 : (arg.i == i ? 0 : 1));
}
}
上面的代码展示了编写存储类型的最基本的方式,HashType放置于HashSet中,TreeType放置于TreeSet中。
5.如果我们尝试着将没有恰当支持必需操作的类型用于需要这些方法的Set,并不会出现任何问题,甚至不会出现运行时异常,那是因为hashCode()和equals()的默认实现是合法的,即便它不正确。但是如果这样就将它们放置到任何散列实现中都会实现重复,这样就违反了Set的基本契约。
一.SortedSet
1.SortedSet中的元素可以保证处于排序状态,也就赋予了它接口外的其他功能:
Method | 功能描述 |
---|---|
Comparator comparator() | 返回当前Set使用的Comparator;或者返回null,表示以自然的方式排序 |
Object first() | 返回容器中第一个元素 |
Object last() | 返回容器中最后一个元素 |
SortedSet subSet(fromElement, toElement) | 生成此Set的子集,范围从fromElement(包含)到toElement(不包含) |
SortedSet headSet(toElement) | 生成此Set的子集,由小于toElement的元素组成 |
SortedSet tailSet(fromElement) | 生成此Set的子集,由大于或等于fromElement的元素组成 |
队列
1.除了并发应用,Queue在Java SE5中仅有的两个实现是LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能:
class QueueBehavior {
private static int count = 10;
static void test(Queue queue, Generator gen) {
for (int i = 0; i < count; i++)
queue.offer(gen.next());
while (queue.peek() != null)
System.out.print(queue.remove() + " ");
System.out.println();
}
static class Gen implements Generator {
String[] s = ("one two three four five six seven eight nine ten").split(" ");
int i;
public String next() {
return s[i++];
}
}
public static void main(String... args) {
test(new LinkedList(), new Gen());
test(new PriorityQueue(), new Gen());
test(new ArrayBlockingQueue(count), new Gen());
test(new ConcurrentLinkedList(), new Gen());
test(new LinkedBlockingQueue(), new Gen());
test(new PriorityBlockingQueue(), new Gen());
}
}
/*
Output:
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
*/
一.优先级队列
1.优先级队列声明下一个弹出的元素是最需要的元素(具有最高级的优先级)。
2.当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是可以容果提供自己的Comparator来修改这个顺序。
3.在这里,重复时允许的,最小的值拥有组最高的优先级。
二.双向队列
1.双向队列就像是一个队列,但是你可以在任何一段添加或移除元素。
2.Java标准类库中没有人和显示的用于双向队列的接口。但是,可以使用组合来创建一个Deque类,并直接从LinkedList中暴露相关方法:
public class Deque {
private LinkedList deque = new LinkedList;
public void addFirst(T e) { deque.addFirst(e); }
public void addLast(T e) { deque.addLast(e); }
public T getFirst() { return deque.getFirst(); }
public T getLast() { return deque.getLast(); }
public T removeFirst() { return deque.removeFirst(); }
public T removeLast() { return deque.removeLast(); }
public int size() { rerturn deque.size(); }
}