第0节
是在学习过程中遇到的一些问题,初次看本文可以直接跳到1. Java集合框架介绍
部分。
接下来我们会看到很多的集合接口源码:Collection, Set, List, Queue, Deque
,它们之间会有接口继承的关系,但是我们会发现一个很奇怪的问题,我们细细说来。
我们可以看7. The Deque Interface
中Deque的源码,发现
public interface Deque<E> extends Queue<E> {
// ...
}
Deque是继承自Queue的,而Queue是继承自Collection的。
public interface Queue<E> extends Collection<E> {
// ...
}
但是我们发现在Deque接口的源码中
,仍然列出了继承自Queue的方法
,还有继承自Collection的方法
。我们学过接口的继承知道:子接口会继承父接口的接口方法,这样我们在子接口中不必要再声明父接口中已经存在的抽象方法了。
但是JDK中Java集合框架的接口继承源码中并没有这样子,而是在每个接口中都会重新列出一部分父接口的抽象方法。
为啥Java集合框架的开发者不好好利用继承的代码重用特性呢?下面是网络上给出的答案,我觉得挺好。
- java编程思想 第四版 第231页有这么一段话:
Set具有与Collection完全一样的接口方法,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)
仅仅为了代码的可读性。从语法上说的确不是必须的。
即使同一个方法,上面的注释是不一样的。
API就是要让人看懂,在A集合里定义a方法,解释跟A有关的信息
然后B集合继承A集合,同样有a方法,但是要对a方法重新说明一下,怎么办?是不是要重新写一遍?就是这个情况了
collection ,有时也叫做container(容器)——将多个元素组织成一个整体单元的对象。集合被用来存/取/操作数据,与集合数据进行交流。如果你用过Java语言或者其它编程语言,那么你很可能已经熟悉了集合。
collections framework,集合框架——表示/操作集合的统一的架构。所有集合框架都包含下面的部分:
除了Java集合框架,最广为人知的集合框架当然是C++中的STL(Standard Template Library ),Smalltalk中的collection。历史原因,集合框架是非常复杂的,这造成人们认为它有着陡峭的学习曲线。我们相信Java集合框架能打破这种传统。
Java集合框架的好处?
core collection interfaces,核心集合接口
封装了不同的集合类型。如下图所示。这些接口允许集合的操作独立于其具体的实现细节。Core collection interfaces
核心集合接口是Java集合框架的基石。
/*
Collection | Map
↑ | ↑
___________↗↖_____________ | SortedMap
↑ ↑ ↑ ↑ |
Set List Queue Deque |
↑ |
SortedSet |
The core collection interfaces.
*/
注意,上图中Map
并不是一个Collection
记住,所有的核心集合接口都是泛型的。如Collection
接口:
public interface Collection<E>...
表示接口是泛型的。当声明一个Collection
对象时,你能并且你应该指定泛型的类型,也就是集合内含元素的数据类型。指定类型能够让编译器在编译时核实你放进集合的对象类型是正确的,因此会减少运行时错误。更多关于泛型的介绍,可以查看我的 Oracle Java Tutorial专栏 对泛型的介绍: The Java™ Tutorials——(0)Learning the Java Language 之 6.泛型(Generics) 。
当你理解了如何使用这些接口的时候,那么你对Java集合框架也就有一个大概的了解了。这章(2. 集合接口)会给出有效地使用这些接口的一些参考
,包括什么时候使用什么接口
。你将学会每个接口的编程套路并帮助你充分利用它们。
为了保证核心接口数量的可控,Java平台不会为每种接口类型的变体提供单独的接口
。(所谓变体可能包括:immutable
,fixed-size
, append-only
;不可变性/固定大小/只可追加)。相反,每个接口的操作都被设计为可选的
——也就是说极端情况下,某种接口实现可能不支持所有的操作。如果一个接口的实现类不支持的操作被调用时,集合会抛出一个异常 UnsupportedOperationException
。接口的实现API文档中应该要记录它支持哪些可选操作。Java平台的通用实现类一般都支持所有的可选操作。
下面简单介绍一下核心集合接口:
Collection
— 对象,或者说元素的集合。所有集合的公共实现类,以Collection
支持的最通用的操作来使用。一些集合类型允许重复元素存在,一些则不允许。一些是有序的,另一些是无序的。Java平台对Collection
接口提供任何直接的实现类,但是提供了更多的子接口;如Set
,List
。
Set
— 不包含重复元素的集合。此接口模型化了数学上的抽象集合。
List
— "有序"集合(也叫sequence
序列集合)。List
能够包含重复元素,用户能够准确的控制每个元素插入在哪个位置,并且能够通过索引来访问任意元素。如果你使用过Vector
,那么你也会熟悉List
的一般操作。
Queue
— 容纳多个优先处理的元素。除了Collection提供的基本操作外,它还提供其它的插入,取出,检查操作。
Queues 典型实现是以FIFO(first-in, first-out)的方式组织元素,但不绝对。一个例子就是priority queues
优先队列,它会根据comparator
比较器或者元素的大小顺序来组织元素。无论Queues
是那种,它取出队头元素的方式都是通过调用 remove
或者 poll
。FIFO队列中,所有新的元素被插入在queue的尾部。其它的队列可能会使用其它的插入策略。每个Queue
的实现都必须指定它的元素组织属性。
Deque
— 容纳多个优先处理的元素。除了Collection提供的基本操作外,它还提供其它的插入,取出,检查操作。
Deques能够被用作FIFO (first-in, first-out)
和LIFO (last-in, first-out)
。双端队列中所有的元素都能够在两端进行插入/取出/移除。
Map
— 映射key->value的对象。Map
不能包含重复的键值,每个key最多只能映射一个value。如果你用过Hashtable
的话,你可能已经熟悉了Map
的基本操作。
接下来是排序版本的Map
,Sort
SortedSet
。升序Set
集合。提供一些额外的操作来利用它的有序性。可用来存单词表或者成员名单等。SortedMap
。按key升序的Map
集合。可用来存字典或者电话簿等。public interface Collection<E> extends Iterable<E> {
/*******************基操勿6*************************/
// 注意点:很多都有返回boolean值
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
boolean add(E e);
boolean remove(Object o);
/*******************数组操作*************************/
// 兼容旧代码的桥接方法
Object[] toArray();
<T> T[] toArray(T[] a);
/*******************整个集合上的操作******************/
// addAll, removeAll, retainAll返回true,如果当前集合在处理过程中被修改了。
void clear();
boolean retainAll(Collection<?> c);// 取交集
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
/*************************************/
boolean equals(Object o);
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() { //(JDK 8)串行流
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() { //(JDK 8)并行流
return StreamSupport.stream(spliterator(), true);
}
}
Collection
, 对象元素的集合体。Collection
接口被用做普遍用途,如一般所有的collection 实现都有一个构造器接收Collection
类型的参数;这种构造器我们称之为:conversion constructor,它会初始构造一个集合,用传入的集合元素来初始化包含指定集合的所有元素。举个例子:
// c可能为List或者Set
Collection<String> c = ...
// 但是c具体是啥集合类型,我们构造器不关心。
List<String> list = new ArrayList<String>(c);
// or
// List list = new ArrayList<>(c);
有三种方法来遍历集合:
聚合操作
,查看下面的2.1.3节
介绍for-each
construct 。for-each语句for (Object o : collection)
System.out.println(o);
Iterator
s 。迭代器。public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() { // optional
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) { // optional
Objects.requireNonNull(action);
while (hasNext()) action.accept(next());
}
}
Iterator.remove
是在迭代期间修改集合唯一安全的方式。
使用Iterator
而非for-each
的理由:
Remove the current element
。移除当前迭代到的元素。static void filter(Collection<?> c) {
for (Iterator<?> it = c.iterator(); it.hasNext(); )
if (!cond(it.next()))
it.remove();
}
并行迭代多个集合。
JDK8及之后,遍历集合的一个更好的方法就是获取集合的流(stream)
,并对流进行聚合操作(perform aggregate operations on it)
。聚合操作通常结合lambda表达式
来用更少的代码让程序更具表现力
。下面是一个简单例子:
// 打印shapes集合中"红色的"元素
myShapesCollection.stream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
// 当然,我们能够请求一个并行流来进行聚合操作
// 当集合足够大,电脑多核,次方法会更有效
myShapesCollection.parallelStream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
再如,你可能想将Collection
的元素转化
为String
对象,并用逗号分隔将它们合并。
String joined = elements.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
求所有员工的工资总和:
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
聚合操作与addAll,addAll
之类的"块"集合操作的区别。关键区别在于底层集合的变化性。(The key difference between the new aggregate operations and the existing bulk operations (containsAll
, addAll
, etc.) is that the old versions are all mutative, meaning that they all modify the underlying collection. In contrast, the new aggregate operations do not modify the underlying collection. When using the new aggregate operations and lambda expressions, you must take care to avoid mutation so as not to introduce problems in the future, should your code be run later from a parallel stream.)
public interface Set<E> extends Collection<E> {
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
// Modification Operations
boolean add(E e);// 如果已经存在了,返回false
boolean remove(Object o);
Object[] toArray();
<T> T[] toArray(T[] a);
// Bulk Operations
void clear();
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);// 取交集
boolean removeAll(Collection<?> c);
// Comparison and hashing
/**
* Compares the specified object with this set for equality. Returns
* true if the specified object is also a set, the two sets
* have the same size, and every member of the specified set is
* contained in this set (or equivalently, every member of this set is
* contained in the specified set). This definition ensures that the
* equals method works properly across different implementations of the
* set interface.
*
* @param o object to be compared for equality with this set
* @return true if the specified object is equal to this set
*/
boolean equals(Object o);
/**
* Returns the hash code value for this set. The hash code of a set is
* defined to be the sum of the hash codes of the elements in the set,
* where the hash code of a null element is defined to be zero.
* This ensures that s1.equals(s2) implies that
* s1.hashCode()==s2.hashCode() for any two sets s1
* and s2, as required by the general contract of
* {@link Object#hashCode}.
*
* @return the hash code value for this set
* @see Object#equals(Object)
* @see Set#equals(Object)
*/
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
}
Set
接口包含从Collection
继承而来的方法,同时添加了一些限制重复元素的方法。Set
也添加了一个强大的约定
,此约定是关于equals
和hashCode
方法的。Set
实例能够被比较,即使它们的Set
实现是不同的,只要它们含有的元素是相同的,那么我们就认为两个Set
对象相等。
Java平台有3
种Set
的实现:HashSet
,TreeSet
,LinkedHashSet
。
hash table哈希表
中,性能最优,但是迭代顺序没有保证
。red-black红黑树
中,元素保证有序性,比HashTable慢
。(as a hash table with a linked list running through it)
,按照插入的先后排序。LinkedHashSet
避免了由HashSet
提供的未指定的且通常完全无序排序,它的性能比HashTable稍微低一些。 下面给出一个使用Set
的套路,假定你想要 使用Collection c
内的所有元素来创建一个新的Collection,并且去重 ,那么我们可以这样做:
Collection<Type> noDups = new HashSet<Type>(c);
// JDK 8之后,我们可以这样做:
Set<Type> set = c.stream()
.collect(Collectors.toSet()); // no duplicates
// 下面是一个泛型方法,去重并返回一个新的Set
public static <E> Set<E> removeDups(Collection<E> c) {
return new LinkedHashSet<E>(c);
}
统计单词的不同个数:
/*
java FindDups i came i saw i left
4 distinct words: [left, came, saw, i]
*/
// stream
public class FindDups {
public static void main(String[] args) {
Set<String> distinctWords = Arrays.asList(args).stream()
.collect(Collectors.toSet());
System.out.println(distinctWords.size()+
" distinct words: " +
distinctWords);
}
}
// for-each
public class FindDups {
public static void main(String[] args) {
Set<String> s = new HashSet<String>();
for (String a : args) s.add(a);
System.out.println(s.size() + " distinct words: " + s);
}
}
上面的遍历都是引用接口类型Set
而非其实现类;强烈推荐实践过程中就使用这种方式,因为它给我们很大的灵活性来改变其引用的具体实现类,只需要改变构造器就行,如下程序所示。同时,这种方式也防止我们使用任何非标准的操作,如实现类并未按照接口的标准来实现。
Set<String> s = new HashSet<String>();
// change to
Set<String> s = new LinkedHashMap<String>();
/* 此时上面的程序运行结果会是有序的:
java FindDups i came i saw i left
4 distinct words: [came, i, left, saw]
*/
再看一个例子,我们需要找出单词中出现次数1次
和出现多次
的单词:
/*
java FindDups2 i came i saw i left
Unique words: [left, saw, came]
Duplicate words: [i]
*/
public class FindDups2 {
public static void main(String[] args) {
Set<String> uniques = new HashSet<String>();
Set<String> dups = new HashSet<String>();
for (String a : args)
if (!uniques.add(a))// add,如果已经存在会返回false
dups.add(a);
// Destructive set-difference
uniques.removeAll(dups);
System.out.println("Unique words: " + uniques);
System.out.println("Duplicate words: " + dups);
}
}
求代数中的对称差
(symmetric set difference):(s1-s2)|(s2-s1)
其中|
我们认为是集合并操作
Set<Type> symmetricDiff = new HashSet<Type>(s1);
symmetricDiff.addAll(s2);
Set<Type> tmp = new HashSet<Type>(s1);
tmp.retainAll(s2);
symmetricDiff.removeAll(tmp);
Positional access
,位置访问。根据在List中的位置来访问。如get
, set
, add
, addAll
, and remove
.Search
,搜索指定对象,返回其索引。 如indexOf
and lastIndexOf
.Iteration
,继承Iterator
语义, 充分利用list的序列性。如listIterator
Range-view
,范围操作。如sublist
public interface List<E> extends Collection<E> {
// Query Operations
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
// Modification Operations
boolean add(E e);
boolean remove(Object o);
// Bulk Modification Operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
void clear();
// Comparison and hashing
/**
* Compares the specified object with this list for equality. Returns
* true if and only if the specified object is also a list, both
* lists have the same size, and all corresponding pairs of elements in
* the two lists are equal. (Two elements e1 and
* e2 are equal if (e1==null ? e2==null :
* e1.equals(e2)).) In other words, two lists are defined to be
* equal if they contain the same elements in the same order. This
* definition ensures that the equals method works properly across
* different implementations of the List interface.
*
* @param o the object to be compared for equality with this list
* @return true if the specified object is equal to this list
*/
boolean equals(Object o);
/**
* Returns the hash code value for this list. The hash code of a list
* is defined to be the result of the following calculation:
* {@code
* int hashCode = 1;
* for (E e : list)
* hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
* }
* This ensures that list1.equals(list2) implies that
* list1.hashCode()==list2.hashCode() for any two lists,
* list1 and list2, as required by the general
* contract of {@link Object#hashCode}.
*
* @return the hash code value for this list
* @see Object#equals(Object)
* @see #equals(Object)
*/
int hashCode();
// Positional Access Operations
E get(int index);
E set(int index, E element);// 返回设置前的旧值
void add(int index, E element);
E remove(int index);// 返回移除元素的值
// Search Operations
int indexOf(Object o);
int lastIndexOf(Object o);
// List Iterators
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
// View
List<E> subList(int fromIndex, int toIndex);
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
}
List
有两种实现: ArrayList , LinkedList 。
下面给出一个简单的例子:
List<Person> people = ...;
// 取出名字形成list
List<String> list = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
与Set一样,List
也约定了equals
和hashCode
方法,两个List对象能够进行逻辑上的相等比较,无论它们的具体List实现是咋样。当两个list包含相同的元素,且顺序一样,那么它们就相等。
Arrays
方法有一个静态方法asList
public static <T> List<T> asList(T... a)
List
提供的ListIterator
能让你以任意的顺序遍历链表,在遍历时修改链表,获取当前迭代器的索引。
// ListIterator
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
// 而我们知道:Iterator接口如下
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext()) action.accept(next());
}
}
这里给出逆序遍历的代码:
for (ListIterator<Type> it = list.listIterator(list.size()); it.hasPrevious(); ) {
Type t = it.previous();
...
}
/*
6 个 元 素 的 List 中,迭 代 器 可 能 的 光 标 位 置
_|_____|_____|_____|_____|_____|_____|___
| [0] | [1] | [2] | [3] | [4] | [5] |
_|_____|_____|_____|_____|_____|_____|___
↑ ↑ ↑ ↑ ↑ ↑ ↑
Cursor
The 7 possible cursor positions.
_|_____|_
| [i] |
_|_____|_
↑ ↑
cursor
cur.previousIndex(); // [i-1]
cur.nextIndex(); // [i]
*/
Collections
类有很多应用到List
的多态算法。我们列出一个大概:
sort
— 对List排序归并排序,这是一个快速/稳定的排序算法。shuffle
— 随机排列List元素。reverse
— 逆序List。rotate
— 旋转List所有元素给定的distance距离。swap
— 交换List的给定两个元素。replaceAll
— 替换所有值为给定的值为另一个给定的值。fill
— 重写每个元素为给定的元素。copy
— 拷贝一个List到另一个ListbinarySearch
— 二分搜索。indexOfSubList
— 子List的索引lastIndexOfSubList
— 子列表的索引(从逆序开始找)public interface Queue<E> extends Collection<E> {
// 插入
boolean add(E e); //
boolean offer(E e); // 只用在有界队列中
// 移除并返回值
E remove(); // 队空,抛出异常
E poll(); // 队空,返回false
// 只取值
E element();
E peek();
}
**Queue Interface Structure:**
Type of Operation | Throws exception |
Returns special value |
---|---|---|
Insert | add(e) |
offer(e) |
Remove | remove() |
poll() |
Examine | element() |
peek() |
Queue
的每个方法会以两种形式退出:
null
or false
前面说过,不只是有FIFO
队列,还有priority queues
优先队列。不管哪种队列,remove
or poll
总是会移除队头, add(e)
or offer(e)
总是会添加元素到队尾(如果不出现操作失败的话)。
Queue
的实现类可能会有限制元素的个数,我们称这种队列为有界的
。java.util.concurrent
包中存在有界队列的实现, java.util
包中不存在。
add
可能会抛出IllegalStateException
,offer
返回false
表示队列满了。offer 方法只用在有界队列中
。
remove
可能会抛出NoSuchElementException
,poll
返回null
, 队列为空。
element
and peek
只返回值并不移除队头元素。不同于remove
和poll
,如果队列为空:element
抛出NoSuchElementException
,而peek
返回null
。
Queue不允许null
值的插入。当然,如果是Queue的LinkedList
实现,就另当别论了,LinkedList
允许null
值的插入。
Queue的equals
和hashCode
方法并非基于元素实现的,它们是直接继承了Object
的版本。
java.util.concurrent.BlockingQueue
,阻塞队列,继承自Queue。
给个例子,Queue被用来实现一个倒计时
public class Countdown {
public static void main(String[] args) throws InterruptedException {
int time = Integer.parseInt(args[0]);
Queue<Integer> queue = new LinkedList<Integer>();
for (int i = time; i >= 0; i--)
queue.add(i);
while (!queue.isEmpty()) {
System.out.println(queue.remove());
Thread.sleep(1000);
}
}
}
上面这个程序有些意思:
Queue<Integer> queue = new LinkedList<Integer>();
为啥一个Queue能够引用一个List对象?我们先看看LinkedList的源码:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// ...
}
发现LinkedList不仅是一个List,并且还是一个双端队列
,我们看看双端队列的实现:
public interface Deque<E> extends Queue<E> {
// ...
}
双端队列就是一个队列!
。所以一个队列能够引用LinkedList对象
。由于我们的引用类型为Queue队列,那么我们只需要队列的接口特性即可。add
,remove
操作也就合情合理了。
我们再看看优先队列:PriorityQueue
的例子:
static <E> List<E> heapSort(Collection<E> c) {
Queue<E> queue = new PriorityQueue<E>(c);
List<E> result = new ArrayList<E>();
while (!queue.isEmpty())
result.add(queue.remove());
return result;
}
Deque,读为deck,double-ended-queue双端队列的缩写。支持队头队尾的插入/删除操作。
public interface Deque<E> extends Queue<E> {
void addFirst(E e);
void addLast(E e);
boolean offerFirst(E e);
boolean offerLast(E e);
E removeFirst();
E removeLast();
E pollFirst();
E pollLast();
E getFirst();
E getLast();
E peekFirst();
E peekLast();
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
// *** Queue methods ***
boolean add(E e);
boolean offer(E e);
E remove();
E poll();
E element();
E peek();
// *** Stack methods ***
void push(E e);
E pop();
// *** Collection methods ***
boolean remove(Object o);
boolean contains(Object o);
public int size();
Iterator<E> iterator();
Iterator<E> descendingIterator();
}
Deque是一个非常丰富的抽象数据类型,它同时实现了Stack
和Queue
。 ArrayDeque and LinkedList 实现了Deque接口。
Deque接口方法能分成3类:
Insert
。addfirst
和offerFirst
插队头,addLast
和offerLast
插队尾。如果Deque的容量被限制的话,推荐offerFirst
or offerLast
,因为addFirst
当队满时并不会抛出异常。Remove
。removeFirst
,pollFirst
移队首,removeLast
,pollLast
移队尾。Deque
队列为空时,pollFirst
,pollLast
返回null
而removeFirst
,removeLast
抛出异常。Retrieve
。getFirst
,peekFirst
访问队首,getLast
,peekLast
访问队尾。当队空,getFirst
和getLast
抛出异常,peekFirst
和peekLast
返回null
Deque Methods :
Type of Operation | First Element (Beginning of the Deque instance) |
Last Element (End of the Deque instance) |
---|---|---|
Insert | addFirst(e) offerFirst(e) |
addLast(e) offerLast(e) |
Remove | removeFirst() pollFirst() |
removeLast() pollLast() |
Examine | getFirst() peekFirst() |
getLast() peekLast() |
我们注意到API中还有两个方法:
boolean removeFirstOccurrence(Object o);
boolean removeLastOccurrence(Object o);
// 移除第一次出现的指定元素,如果不存在,则返回false
// 两个方法的区别在于查找的起始顺序
Map,key-value键值对的对象。不能包含重复的key,每个key最多一个对应的值。Map模型化了数学上的抽象的函数/映射(function)
。
public interface Map<K,V> {
// Query Operations
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
// Modification Operations
V put(K key, V value);
V remove(Object key);
// Bulk Operations
void putAll(Map<? extends K, ? extends V> m);
void clear();
// Views
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
/*
(Comparator> & Serializable)表示
将结果强制转换为一个实现了Serializable接口的Comparator对象
这是Java8的语法,表示同时强制转换为多种类型
*/
public static <K extends Comparable<? super K>, V>
Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
public static <K, V extends Comparable<? super V>>
Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
public static <K, V> Comparator<Map.Entry<K, V>>
comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
public static <K, V> Comparator<Map.Entry<K, V>>
comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
}
// Comparison and hashing
boolean equals(Object o);
int hashCode();
// Defaultable methods
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
}
Map有3种实现: HashMap , TreeMap , and LinkedHashMap 。性能关系与Set对应的类一样。
// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// Compute sum of salaries by department
Map<Department, Integer> totalByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.summingInt(Employee::getSalary)));
// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade()>= PASS_THRESHOLD));
// Classify Person objects by city
Map<String, List<Person>> peopleByCity
= personStream.collect(Collectors.groupingBy(Person::getCity));
// Cascade Collectors
Map<String, Map<String, List<Person>>> peopleByStateAndCity
= personStream.collect(Collectors.groupingBy(Person::getState,
Collectors.groupingBy(Person::getCity)))
下面看一个统计单词数量的例子:
/*
java Freq if it is to be it is up to me to delegate
8 distinct words:
{to=3, delegate=1, be=1, it=2, up=1, if=1, me=1, is=2}
// 如果将下面程序改为TreeMap,那么会按key字典排序
8 distinct words:
{be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}
// 类似地,改为LinkedHashMap,则
8 distinct words:
{if=1, it=2, is=2, to=3, be=1, up=1, me=1, delegate=1}
*/
public class Freq {
public static void main(String[] args) {
Map<String, Integer> m = new HashMap<String, Integer>();
// Initialize frequency table from command line
for (String a : args) {
Integer freq = m.get(a);
m.put(a, (freq == null) ? 1 : freq + 1);
}
System.out.println(m.size() + " distinct words:");
System.out.println(m);
}
}
和Set,List一样,Map
也加强了equals
和hashCode
方法使得任意两个Map实现类可以进行逻辑上的相等比较。如果它们有完全一致的key-value对,那么两个Map相等。
使用已存在的map来构造HashMap
// 这是HashMap的一个构造器
public HashMap(Map<? extends K, ? extends V> m) {
// ...
}
Map<K, V> copy = new HashMap<K, V>(m);
// 块操作
static <K, V> Map<K, V> newAttributeMap(Map<K, V>defaults, Map<K, V> overrides) {
Map<K, V> result = new HashMap<K, V>(defaults);
result.putAll(overrides);
return result;
}
将Map视为集合,可通过下面三种方法:
keySet
—key的集合,为Set。Set keySet();
values
— value的集合,此集合并非Set。Collection values();
entrySet
— key-value对的集合,为Set。Set> entrySet();
其中Map.Entry为Entry嵌套接口
。for (KeyType key : m.keySet())
System.out.println(key);
// Filter a map based on some
// property of its keys.
for (Iterator<Type> it = m.keySet().iterator(); it.hasNext(); )
if (it.next().isBogus())
it.remove();
for (Map.Entry<KeyType, ValType> e : m.entrySet())
System.out.println(e.getKey() + ": " + e.getValue());
在迭代期间,修改Map的唯一安全方法就是通过Entry的setValue方法Map.Entry's setValue。
判断一个Map是另一个Map的子Map
if (m1.entrySet().containsAll(m2.entrySet())) {
...
}
判断两个Map是否具有完全一致的键值对
if (m1.keySet().equals(m2.keySet())) {
...
}
判断Map的键值完全涵盖给定的requiredAttrs
属性集,并且,键值集合属于给定的permittedAttrs
。(数学上表示为:requiredAttrs
集为Map的键值集的子集,键值集的子集又是permittedAttrs
的子集 )。
static <K, V> boolean validate(Map<K, V> attrMap, Set<K> requiredAttrs, Set<K>permittedAttrs) {
boolean valid = true;
Set<K> attrs = attrMap.keySet();
if (! attrs.containsAll(requiredAttrs)) {
Set<K> missing = new HashSet<K>(requiredAttrs);
missing.removeAll(attrs);
System.out.println("Missing attributes: " + missing);
valid = false;
}
if (! permittedAttrs.containsAll(attrs)) {
Set<K> illegal = new HashSet<K>(attrs);
illegal.removeAll(permittedAttrs);
System.out.println("Illegal attributes: " + illegal);
valid = false;
}
return valid;
}
还有一些例子:
// 两个map m1,m2的交集
Set<KeyType>commonKeys = new HashSet<KeyType>(m1.keySet());
commonKeys.retainAll(m2.keySet());
// 移除公有entry
m1.entrySet().removeAll(m2.entrySet());
// 移除共有key集
m1.keySet().removeAll(m2.keySet());
//
Set<Employee> individualContributors = new HashSet<Employee>(managers.keySet());
individualContributors.removeAll(managers.values());
Employee simon = ... ;
managers.values().removeAll(Collections.singleton(simon));
Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers);
m.values().removeAll(managers.keySet());
Set<Employee> slackers = m.keySet();
下面我们用一个具体的例子来看看上面的用法。
public class CollectionTest {
public static int bound = 2;
public static int gerRandom(int bound){
return new Random().nextInt(bound);
}
public static void put(int rand, Map<String, Integer> map){
for(int i = 1; i <= 10 ; i ++ ){
rand = gerRandom(bound);
if(rand == 0) map.put(String.valueOf(i), i);
if(rand == 1) map.put(String.valueOf(i), i+1);
System.out.printf("rand1: %d -- [%d, %d]%n", rand, i, rand==0?i:i+1);
}
}
public static void main(String[] args) throws IOException {
Map<String, Integer> map1 = new HashMap<>();
Map<String, Integer> map2 = new HashMap<>();
System.out.println("------------Map1-------------");
put(gerRandom(bound), map1);
System.out.println("------------Map2-------------");
put(gerRandom(bound), map2);
//map1.keySet().removeAll(map2.keySet()); // way 1
//map1.values().removeAll(map2.values()); // way 2
//map1.entrySet().removeAll(map2.entrySet()); // way 3
//map1.keySet().retainAll(map2.keySet()); // way 4
//map1.values().retainAll(map2.values()); //way 5
//map1.entrySet().retainAll(map2.entrySet()); // way 6
for(Map.Entry<String, Integer> entry : map1.entrySet()){
System.out.printf("[%s, %d]%n", entry.getKey(), entry.getValue());
}
}
}
Java并没有Multimap的接口。但是我们可以将Map的value类型设置为List
类型来表示多重映射。下面给出一个例子:
// anagram:相同字母的异构词( same letters but in a different order)
// anagram group:称之为异构集
// 程序作两件事情:
// (1) 读入字典文件名
// (2) 输出大小>=指定整数的异构集
// 可想而知对于一个非空词典,输出会 >= 1
public class Anagrams {
public static void main(String[] args) {
int minGroupSize = Integer.parseInt(args[1]);
// Read words from file and put into a simulated multimap
Map<String, List<String>> m = new HashMap<String, List<String>>();
try {
Scanner s = new Scanner(new File(args[0]));
while (s.hasNext()) {
String word = s.next();
String alpha = alphabetize(word);
List<String> l = m.get(alpha);
if (l == null)
m.put(alpha, l=new ArrayList<String>());
l.add(word);
}
} catch (IOException e) {
System.err.println(e);
System.exit(1);
}
// Print all permutation groups above size threshold
for (List<String> l : m.values())
if (l.size() >= minGroupSize)
System.out.println(l.size() + ": " + l);
}
private static String alphabetize(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);
return new String(a);
}
}
LIst
能通过如下方式来排序
Collections.sort(l);
List
会按照字典排序。如果是Date
类型,则会按时间的先后排序。这种排序是怎么做到的呢?这是因为String
和Date
类型都实现了Comparable
接口:
package java.lang;
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable
的实现类一般都提供类的自然有序(natural ordering)
,它让类的对象自动排序。下面表列出了Java平台中实现Comparable接口的类:
Classes Implementing Comparable | |
---|---|
Class | Natural Ordering |
Byte |
Signed numerical |
Character |
Unsigned numerical |
Long |
Signed numerical |
Integer |
Signed numerical |
Short |
Signed numerical |
Double |
Signed numerical |
Float |
Signed numerical |
BigInteger |
Signed numerical |
BigDecimal |
Signed numerical |
Boolean |
Boolean.FALSE < Boolean.TRUE |
File |
System-dependent lexicographic on path name |
String |
Lexicographic |
Date |
Chronological |
CollationKey |
Locale-specific lexicographic |
如果你尝试排序一个List
,此ClassType并没有实现Comparable,那么 Collections.sort(list)将会抛出一个 ClassCastException 异常
。类似地,如果list的元素并不能使用comparator来相互比较的话,调用Collections.sort(list, comparator)会抛出ClassCastException 异常
。
最开始我们列出的Comparable接口源码
如下,所以我们只需实现其compareTo
接口即可
public interface Comparable<T> {
public int compareTo(T o);
}
// compareTo返回值
// < 0 传入的对象小于当前对象
// == 0 传入的对象等于当前对象
// > 0 传入的对象大于当前对象
// 如果当前对象与传入的对象之间不可比较
// 则抛出一个 ClassCastException 异常
下面给出一个例子:
public class Name implements Comparable<Name> {
private final String firstName, lastName;
public Name(String firstName, String lastName) {
if (firstName == null || lastName == null)
throw new NullPointerException();
this.firstName = firstName;
this.lastName = lastName;
}
public String firstName() { return firstName; }
public String lastName() { return lastName; }
public boolean equals(Object o) {
if (!(o instanceof Name))
return false;
Name n = (Name) o;
return n.firstName.equals(firstName) && n.lastName.equals(lastName);
}
public int hashCode() {
return 31*firstName.hashCode() + lastName.hashCode();
}
public String toString() {
return firstName + " " + lastName;
}
public int compareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
}
}
对上面的Name
类,注意以下几点
Name
是不可变的(immutable)。null
。hashCode
被重新定义。重写hashcode
,务必要重写equals
方法。因为相等的对象一定有相同的hashcode
。toString
方法返回可读的字符串。 让我们来测试一下代码:
// 输出:
// [Karl Ng, Tom Rich, Jeff Smith, John Smith]
public class NameSort {
public static void main(String[] args) {
Name nameArray[] = {
new Name("John", "Smith"),
new Name("Karl", "Ng"),
new Name("Jeff", "Smith"),
new Name("Tom", "Rich")
};
List<Name> names = Arrays.asList(nameArray);
Collections.sort(names);
System.out.println(names);
}
}
package java.util;
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
default <U> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator) {
return thenComparing(comparing(keyExtractor, keyComparator));
}
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor) {
return thenComparing(comparing(keyExtractor));
}
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor){
return thenComparing(comparingDouble(keyExtractor));
}
// 逆序
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
// 自然顺序
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator) {
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor){
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1),
keyExtractor.applyAsInt(c2));
}
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T>
keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1),
keyExtractor.applyAsLong(c2));
}
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T>
keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1),
keyExtractor.applyAsDouble(c2));
}
}
你是否想排序对象,让他们不仅仅是自然有序(natural ordering)?你是否想排序对象,对象并不实现Comparable
接口?Comparator
接口与Comparable
接口类似。
public class EmpSort {
static final Comparator<Employee> SENIORITY_ORDER =
new Comparator<Employee>() {
public int compare(Employee e1, Employee e2) {
return e2.hireDate().compareTo(e1.hireDate());
}
};
// Employee database
static final Collection<Employee> employees = ... ;
public static void main(String[] args) {
List<Employee> e = new ArrayList<Employee>(employees);
Collections.sort(e, SENIORITY_ORDER);
System.out.println(e);
}
}
注意,不能使用:
return -r1.hireDate().compareTo(r2.hireDate();
来表示其相对应的逆序排序比较器。
这里是原因:补码知识解惑:-Integer.MIN_VALUE == Integer.MIN_VALUE;
这里有相应代码改进:
static final Comparator<Employee> SENIORITY_ORDER =
new Comparator<Employee>() {
public int compare(Employee e1, Employee e2) {
int dateCmp = e2.hireDate().compareTo(e1.hireDate());
if (dateCmp != 0)
return dateCmp;
return (e1.number() < e2.number() ? -1 :
(e1.number() == e2.number() ? 0 : 1));
}
};
我们不要尝试使用下面代码
来替换上面的代码:
return e1.number() - e2.number();
i足够大,j足够小。
i - j
很可能会溢出,返回一个负数。
A SortedSet
is a Set
that maintains its elements in ascending order, sorted according to the elements’ natural ordering or according to a Comparator
provided at SortedSet
creation time. In addition to the normal Set
operations, the SortedSet
interface provides operations for the following:
SortedSet
也是一个 Set
,只不过它的元素是根据元素的自然顺序(natural ordering )
或者SortedSet
创建时提供的 Comparator
上升排序的。除了继承了Set的操作外,它还提供了如下操作:
Range view
— 有序Set任意range(范围)的操作。Endpoints
— 返回sorted set的首/尾元素。returns the first or last element in the sorted setComparator access
— 返回Comparator
。SortedSet
接口如下:
public interface SortedSet<E> extends Set<E> {
Comparator<? super E> comparator();// Comparator
SortedSet<E> subSet(E fromElement, E toElement);//Range view
SortedSet<E> headSet(E toElement);// Range View
SortedSet<E> tailSet(E fromElement);// Range View
E first();// Endpoint
E last();// Endpoint
@Override
default Spliterator<E> spliterator() {
return new Spliterators.IteratorSpliterator<E>(
this, Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED) {
@Override
public Comparator<? super E> getComparator() {
return SortedSet.this.comparator();
}
};
}
}
SortedSet
的一些集合操作,行为上与一般的Iterator不同。
Iterator()
迭代时,是按顺序迭代
,返回有序的数组。 注意!Java平台的SortedSet实现类的toString()方法会返回排序后的串元素,虽然接口不保证,但是Java平台是保证的。
注意Range View
的含义,通过view返回的set,是原来set上的一个view,或者说引用。我们对view上的操作,等效于对原set上操作。
/* 输出为:
[6, 5, 4, 3, 2, 1]
[6, 5, 2, 1]
*/
public static void main(String[] args) {
List<Integer> list = Arrays.asList(5, 1, 3, 2, 4, 6, 3, 1);
SortedSet<Integer> set = new TreeSet<>(Comparator.reverseOrder());
set.addAll(list);
System.out.println(set);
set.subSet(4, 2).clear();
System.out.println(set);
}
再来讨论SortedSet
为啥提供一个访问比较器的方法?comparator()
返回比较器,这样方便将set以相同的顺序拷贝进一个新的sorted set。我们可以看看TreeSet的一个构造器:
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
与SortedSet类似。SortedMap也有着类似的特性。
Range view
Endpoints
Comparator access
public interface SortedMap<K,V> extends Map<K,V> {
Comparator<? super K> comparator();
SortedMap<K,V> subMap(K fromKey, K toKey);
SortedMap<K,V> headMap(K toKey);
SortedMap<K,V> tailMap(K fromKey);
K firstKey();
K lastKey();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
}