The Java™ Tutorials——(6)Collections—— Interfaces

Java集合框架基础

Java集合框架接口查阅目录

  • Java集合框架基础
    • 0. Java集合框架接口源码的一些说明
    • 1. Java集合框架介绍
    • 2. 集合接口
    • 3 The Collection Interface
      • 3.1 基础API
      • 3.2 遍历(Traversing Collections)
      • 3.3 聚合操作(Aggregate Operations)
    • 4. The Set Interface
      • 4.1 基础API
      • 4.2 API使用
    • 5. The List Interface
      • 5.1 Iterator
      • 5.2 List Algorithms
    • 6. The Queue Interface
    • 7. The Deque Interface
    • 8. The Map Interface
      • 8.1 聚合操作
      • 8.2 基本用法
      • 8.3 集合视图
      • 8.4 Map在代数上的应用
    • 9. 多重映射(Multimaps)
    • 10. 对象排序
      • 10.1 Comparable
      • 10.2 Comparator
    • 11. The SortedSet Interface
    • 12. The SortedMap Interface

0. Java集合框架接口源码的一些说明

第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方法重新说明一下,怎么办?是不是要重新写一遍?就是这个情况了

1. Java集合框架介绍

collection ,有时也叫做container(容器)——将多个元素组织成一个整体单元的对象。集合被用来存/取/操作数据,与集合数据进行交流。如果你用过Java语言或者其它编程语言,那么你很可能已经熟悉了集合。

collections framework,集合框架——表示/操作集合的统一的架构。所有集合框架都包含下面的部分:

  • Interfaces: 接口。代表集合的抽象数据类型。接口允许集合的操作独立于集合的表示细节。面向对象语言中,接口通常会有继承。
  • Implementations: 实现。集合接口的具体实现。本质上讲,它们是可重用的数据结构。
  • Algorithms: 算法。执行有用的计算的方法,如对集合接口搜索,排序实现。这种算法我们认为是多态的,意思是说相同的方法在不同的集合中不同的实现。本质上,算法是可复用的函数。

​ 除了Java集合框架,最广为人知的集合框架当然是C++中的STL(Standard Template Library ),Smalltalk中的collection。历史原因,集合框架是非常复杂的,这造成人们认为它有着陡峭的学习曲线。我们相信Java集合框架能打破这种传统。

Java集合框架的好处?

  • Reduces programming effort: 降低编程精力。通过提供有用的数据结构和算法,Java集合解放你的双手,让你更关注于程序的逻辑实现而不需要特意关注底层设施。通过促进无关API之间的互操作性,Java集合框架使您不用编写适配器对象或转换代码来连接API。
  • Increases program speed and quality: 提升程序速度和质量。Java集合提供高性能,高质量的常用数据结构和算法的实现。每个接口的不同实现都是可互换的,所以程序能很轻松地切换集合实现类。 由于你不必要亲手编写自己地数据结构,所以你有更多地实践来将注意力集中到提升程序地质量和性能上。
  • Allows interoperability among unrelated APIs: 允许不相关的API之间互相操作。集合接口是API来回传递集合的地方语言。如果我的网络管理API提供了一组节点名,并且您的GUI工具箱需要一组列标题,那么我们的API将无缝地进行互操作,即使它们是独立编写的。(明白了再注释)
  • Reduces effort to learn and to use new APIs: 降低学习/使用新API的精力。许多API会将集合作为输入参数,输出结果。在过去,每个这样的API都有小的子API来操作集合。在这些子API中几乎没有一致性,所以你不得不每个API都痛苦地学习一下,并且在使用时容易出错。Java集合框架的标准集合接口,让这种问题消失了。
  • Reduces effort to design new APIs: 降低设计新API的精力。对于设计者和实现者来说,如果它们创造的新API依赖集合,那么它们可以直接使用标准集合接口即可,不必重新发明造轮子。
  • Fosters software reuse: 醋精软件重用。符合标准集合接口的新数据结构本质上是可重用的。这同样适用于对实现这些接口的对象进行操作的新算法。

2. 集合接口

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平台不会为每种接口类型的变体提供单独的接口。(所谓变体可能包括:immutablefixed-sizeappend-only;不可变性/固定大小/只可追加)。相反,每个接口的操作都被设计为可选的——也就是说极端情况下,某种接口实现可能不支持所有的操作。如果一个接口的实现类不支持的操作被调用时,集合会抛出一个异常 UnsupportedOperationException 。接口的实现API文档中应该要记录它支持哪些可选操作。Java平台的通用实现类一般都支持所有的可选操作。

​ 下面简单介绍一下核心集合接口:

  • Collection — 对象,或者说元素的集合。所有集合的公共实现类,以Collection 支持的最通用的操作来使用。一些集合类型允许重复元素存在,一些则不允许。一些是有序的,另一些是无序的。Java平台对Collection接口提供任何直接的实现类,但是提供了更多的子接口;如SetList

  • 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的基本操作。

接下来是排序版本的MapSort

  • SortedSet。升序Set集合。提供一些额外的操作来利用它的有序性。可用来存单词表或者成员名单等。
  • SortedMap 。按key升序的Map集合。可用来存字典或者电话簿等。

3 The Collection Interface

3.1 基础API

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);

3.2 遍历(Traversing Collections)

有三种方法来遍历集合:

  • (1) using aggregate operations聚合操作,查看下面的2.1.3节介绍
  • (2) with the for-each construct 。for-each语句
for (Object o : collection)
    System.out.println(o);
  • (3) by using Iterators 。迭代器。
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();
}
  • 并行迭代多个集合。

3.3 聚合操作(Aggregate Operations)

​ 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.)

4. The Set Interface

4.1 基础API

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 也添加了一个强大的约定,此约定是关于equalshashCode 方法的。Set 实例能够被比较,即使它们的Set实现是不同的,只要它们含有的元素是相同的,那么我们就认为两个Set对象相等。

​ Java平台有3Set的实现:HashSetTreeSetLinkedHashSet

  • HashSet 。 元素存储在hash table哈希表中,性能最优,但是迭代顺序没有保证
  • TreeSet 。元素存储在red-black红黑树中,元素保证有序性,比HashTable慢
  • LinkedHashSet 。元素存储在带有链表的哈希表中,(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);
}

4.2 API使用

​ 统计单词的不同个数:

																											/*
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);

5. The List Interface

  • 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 有两种实现: ArrayListLinkedList

​ 下面给出一个简单的例子:

List<Person> people = ...;

// 取出名字形成list
List<String> list = people.stream()
											.map(Person::getName)
											.collect(Collectors.toList());

​ 与Set一样,List 也约定了equalshashCode 方法,两个List对象能够进行逻辑上的相等比较,无论它们的具体List实现是咋样。当两个list包含相同的元素,且顺序一样,那么它们就相等。

Arrays 方法有一个静态方法asList

public static <T> List<T> asList(T... a)

5.1 Iterator

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]
																							                      */

5.2 List Algorithms

Collections 类有很多应用到List 的多态算法。我们列出一个大概:

  • sort — 对List排序归并排序,这是一个快速/稳定的排序算法。
  • shuffle — 随机排列List元素。
  • reverse — 逆序List。
  • rotate — 旋转List所有元素给定的distance距离。
  • swap — 交换List的给定两个元素。
  • replaceAll — 替换所有值为给定的值为另一个给定的值。
  • fill — 重写每个元素为给定的元素。
  • copy — 拷贝一个List到另一个List
  • binarySearch — 二分搜索。
  • indexOfSubList — 子List的索引
  • lastIndexOfSubList — 子列表的索引(从逆序开始找)

6. The Queue Interface

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可能会抛出IllegalStateExceptionoffer返回false表示队列满了。offer 方法只用在有界队列中

remove可能会抛出NoSuchElementExceptionpoll返回null, 队列为空。

element and peek只返回值并不移除队头元素。不同于removepoll,如果队列为空:element抛出NoSuchElementException,而peek 返回null

​ Queue不允许null 值的插入。当然,如果是Queue的LinkedList 实现,就另当别论了,LinkedList允许null值的插入。

​ Queue的equalshashCode 方法并非基于元素实现的,它们是直接继承了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队列,那么我们只需要队列的接口特性即可。addremove操作也就合情合理了。

​ 我们再看看优先队列: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;
}

7. The Deque Interface

​ 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是一个非常丰富的抽象数据类型,它同时实现了StackQueueArrayDeque and LinkedList 实现了Deque接口。

​ Deque接口方法能分成3类:

  • InsertaddfirstofferFirst 插队头,addLastofferLast 插队尾。如果Deque的容量被限制的话,推荐offerFirst or offerLast ,因为addFirst 当队满时并不会抛出异常。
  • RemoveremoveFirstpollFirst 移队首,removeLastpollLast 移队尾。Deque队列为空时,pollFirstpollLast 返回nullremoveFirstremoveLast 抛出异常。
  • RetrievegetFirstpeekFirst 访问队首,getLastpeekLast 访问队尾。当队空,getFirstgetLast 抛出异常,peekFirstpeekLast 返回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
// 两个方法的区别在于查找的起始顺序

8. The Map Interface

​ 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对应的类一样。

8.1 聚合操作

// 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)))

8.2 基本用法

​ 下面看一个统计单词数量的例子:

/*
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 也加强了equalshashCode 方法使得任意两个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;
}

8.3 集合视图

​ 将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。

8.4 Map在代数上的应用

判断一个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());
        }
    }
}

9. 多重映射(Multimaps)

​ 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);
    }
}

10. 对象排序

LIst能通过如下方式来排序

Collections.sort(l);

List会按照字典排序。如果是Date 类型,则会按时间的先后排序。这种排序是怎么做到的呢?这是因为StringDate类型都实现了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 异常

10.1 Comparable

​ 最开始我们列出的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);
    }
}

10.2 Comparator

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很可能会溢出,返回一个负数。

11. The SortedSet Interface

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 set
  • Comparator 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()迭代时,是按顺序迭代
  • ``toArray ,返回有序的数组。

注意!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);
}

12. The SortedMap Interface

​ 与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();
}

你可能感兴趣的:(Java,™,8,Tutorial)