Java之集合框架
2015-12-11 10:55
531 查看
18.1 集合概述
1.集合框架在设计上需要满足几个目标。
首先,框架必须是高性能的。基本集合(动态数组、链表、树以及哈希表)的实现是高效的。很少需要手动编写这些”数据 引擎”中的某个。
其次,框架必须允许不同类型的集合以类似的方式进行工作,并且具有高度的互操作性。
再次,扩展或改造集合必须易于实现。
最后,必须添加可以将标准数组继承到集合框架中的机制。
2.算法是集合机制中的另一个重要部分。算法操作集合,并且被定义为Collections类中的静态方法。因此,所有集合都可以使用它们。每个集合都不需要实现特定于自己的版本,所发为操作集合提供了标准的方式。
3.于集合密切相关的另个一内容是Iterator接口。迭代器为访问集合中的元素提供了通用、标准的方式,每次访问一个元素。因此,迭代器提供了枚举集合内容的一种方式。因为每个集合都提供了迭代器,所以可以通过Iterator定义的方法访问所有集合类的元素。因此,对循环遍历集合的代码进行很小的修改,就可以将其用于循环遍历列表。
4.JDK 8添加了另一种类型的迭代器,叫做spliterator.简单的说,spliterator就是为并行迭代提供支持的迭代器。支持
spliterator的接口有Spliterator和支持基本类型的几个嵌入式接口。JDK 8还添加了用于基本类型的迭代器接口,例如
PrimitiveIterator和 PrimitiveIterator.OfDouble。
18.3 集合接口
1.Collection接口
2. List接口
3. Set接口
4. SortedSet接口
5. NavigableSet接口
6. Queue接口
7. Deque接口
18.4 集合类
1. ArrayList类
2. LinkedList类
3. HashSet类
4. LinkedHashSet类
5. TreeSet类
6. PriorityQueue类
7. ArrayDeque类
8. EnumSet类
18.5 通过迭代器访问集合
Iterator接口声明的方法:
void forEachRemainning(Consumer
18.5.2 使用for-each循环替代迭代器
18.6 Spliterator
18.9 使用映射
18.9.1 映射接口
1. Map接口
2. SortedMap接口
3. NavigableMap接口
4. Map.Entry接口
18.9.2 映射类
1. HashMap类
2. TreeMap类
3. LinkedHashMap类
18.10 比较器
使用比较器:
18.11 集合算法
18.12 Arrays类
18.13 遗留的类和接口
出自:《Java 8编程参考官方教程(第9版)》
1.集合框架在设计上需要满足几个目标。
首先,框架必须是高性能的。基本集合(动态数组、链表、树以及哈希表)的实现是高效的。很少需要手动编写这些”数据 引擎”中的某个。
其次,框架必须允许不同类型的集合以类似的方式进行工作,并且具有高度的互操作性。
再次,扩展或改造集合必须易于实现。
最后,必须添加可以将标准数组继承到集合框架中的机制。
2.算法是集合机制中的另一个重要部分。算法操作集合,并且被定义为Collections类中的静态方法。因此,所有集合都可以使用它们。每个集合都不需要实现特定于自己的版本,所发为操作集合提供了标准的方式。
3.于集合密切相关的另个一内容是Iterator接口。迭代器为访问集合中的元素提供了通用、标准的方式,每次访问一个元素。因此,迭代器提供了枚举集合内容的一种方式。因为每个集合都提供了迭代器,所以可以通过Iterator定义的方法访问所有集合类的元素。因此,对循环遍历集合的代码进行很小的修改,就可以将其用于循环遍历列表。
4.JDK 8添加了另一种类型的迭代器,叫做spliterator.简单的说,spliterator就是为并行迭代提供支持的迭代器。支持
spliterator的接口有Spliterator和支持基本类型的几个嵌入式接口。JDK 8还添加了用于基本类型的迭代器接口,例如
PrimitiveIterator和 PrimitiveIterator.OfDouble。
18.3 集合接口
集合框架定义了一些核心接口,核心接口决定了集合类的本质特性。换句话说,具体类只是提供了标准接口的不同实现。
1.Collection接口
Collection接口是构建集合框架的基础,因为定义集合的所有类都必须实现该接口。 Collection接口是泛型接口,其声明如下: interface Collection<E> 其中,E指定了集合将要存储的对象的类型。Collection扩展了Iterable接口,这意味着所有集合都可以使用for-each风格的for循环进行遍历(回想一下,只有实现了Iterable接口的类才能通过for循环进行遍历)。 Collection声明了所有集合都将拥有的核心方法。 通过调用add()方法可以将对象添加到集合中。 通过调用addAll()方法,可以将一个集合的所有内容添加到另一个集合中。 通过使用remove()方法可以移除一个对象。 为了移除一组对象,需要调用removeAll()方法。 通过调用retainAll()方法,可以移除除了指定元素之外的所有元素。 要想移除满足某些条件的元素,可以使用removeIf()方法(Predicate是JDK 8新增的一个函数式接口)。 为了清空集合,可以调用clear()方法。 toArray()方法返回一个数组,其中包含调用集合中存储的元素。 通过调用equals()方法可以比较两个集合的相等性。"相等"的精确含义根据集合的不同可以有所区别。 另一个重要的方法是iterator(),它返回集合的一个迭代器。新的soliterator()方法返回集合的一个spliterator。当操作集合时会频繁用到迭代器。最后,stream()和parallelStream()方法返回使用集合作为元素来源的流(第29章将详细讨论新的Stream接口)。
2. List接口
List接口扩展了Collection,并且声明了用来存储一连串元素的集合的行为。在列表中可以使用从0开始的索引,通过元素的位置插入或访问元素。列表可以包含重复的元素。List是泛型接口,其声明如下: interface List<E> 其中,E指定了将存储与列表中的对象的类型。 除了Collection定义的方法外,List还定义了自己的一些方法。 为了获得存储在特定位置的元素,使用对象的索引调用get()方法。为了给列表中的元素赋值,可以调用set()方法,并指定将要修改的对象的索引。为了查找对象的索引,可以使用indexOf()或laseIndexOf()方法。 通过调用subList()方法,指定子列表的开始索引和结束索引,可以获得列表的子列表。 List定义的sort()方法是排序列表的一种方法。
3. Set接口
Set接口定义了组(set)。它扩展了Collection接口,并且声明了集合中不允许有重复元素的组行为。所以,如果为组添加重复的元素,add()方法就会返回false。Set接口没有定义自己的其他方法。Set是泛型接口,其声明如下: interface Set<E> 其中,E指定了组将包含的对象的类型。
4. SortedSet接口
SortedSet接口扩展了Set接口,并且声明了以升序进行排序的组行为。SortedSet是泛型接口,其声明如下: interface SortedSet<E>
5. NavigableSet接口
NavigableSet接口扩展了SortedSet接口,并且该接口声明了支持基于最接近匹配原则检索元素的集合行为。 NavigableSet是泛型接口,其声明如下: interface NavigableSet<E>
6. Queue接口
Queue接口扩展了Collection接口,并且声明了队列的行为,队列通常是先进先出的列表。但是,还有基于其他准则的队列类型。Queue是泛型接口,其声明如下: interface Queue<E>
7. Deque接口
Deque接口扩展了Queue接口,并声明了双端队列的行为。双端队列既可以想标准队列那样先进先出,也可以像堆栈那样后进先出。Deque是泛型接口,其声明如下: interface Deque<E> 注意Deque接口提供了push()和pop()方法。这些方法使得Deque接口的功能与堆栈类似。
18.4 集合类
现在已经熟悉了集合接口,下面开始分析实现它们的标准类。其中的一些类提供了可以使用的完整实现。其他一些类是抽象的,它们提供了可以作为创建具体集合开始点的大体实现。作为一般规则,集合类不是同步的,但是在后面章节将会看到,可以获得它们的同步版本。 注意: 除了集合类之外,还有一些遗留的类,例如,Vector、Stack以及Hashtable,也进行了重新设计以支持集合。
1. ArrayList类
ArrayList类扩展了AbstractList类并实现了List接口。ArrayList是泛型类,其声明如下: class ArrayList<E> 在Java中,标准数组的长度是固定的。ArrayList类支持能够按需增长的动态数组。ArrayList就是元素为对象引用的长度可变的数组。 注意:遗留类Vector也支持动态数组。 ArrayList具有如下所示的构造函数: ArrayList() ArrayList(Collection<? extends E> c) ArrayList(int capacity) 第1个构造函数构建了一个空的数组列表。 第2个构造函数构建了一个数组列表,使用集合c的元素进初始化。 第3个构造函数构建了一个初始容量为capacity的数组。 容量是用于存储元素的数组的大小,当数组中的元素等于容量时,当在向数组列表中添加元素时,容量会自动增长。 尽管存储对象时,ArrayList对象的容量会自动增长,但是可以调用ensureCapacity()方法以手动增长ArrayList对象的容量。如果事先知道将在集合中存储的元素比当前保存的元素多很多,你可能希望这么做。在开始时,一次性增加容量,从而避免以后多次重新分配内存。因为重新分配内存很耗时,阻止不必要的内存分配次数可以提高性能。 ensureCapacity()方法的签名如下: void ensureCapacity(int cap) 其中,cap指定集合新的最小容量。 相反,如果希望减小ArrayList对象数组的大小,进而使其大小精确的等于当前容纳的元素的数量,可以调用trimToSize()方法,该方法如下所示: void trimToSize()
2. LinkedList类
LinkedList类扩展了AbstractSequentialList类,实现了List、Deque以及Queue接口,并且它还提供了一种链表数据结构。 LinkedList是泛型类,其声明如下: class LinkedList<E> LinkedList具有两个构造函数,如下所示: LinkedList() LinkedList(Collection<? extends E> c)
3. HashSet类
HashSet类扩展了AbstractSet类并实现了Set接口,该类用于创建使用哈希表存储元素的集合。HashSet是泛型类,其声明如下: class HashSet<E> 哈希表使用称之为散列的机制存储信息。在散列机制中,键的信息用于确定唯一的值,称为哈希码。然后将哈希码用作索引,在索引位置存储与键关联的数据。将键转换为哈希码是自动执行的————你永远不会看到哈希码本身。此外,你的代码不能直接索引哈希表。散列机制的优势是add()、contains()、remove()以及size()方法的执行时间保持不变,即使是对于比较大的数组也是如此。 HashSet类定义了一下构造函数: HashSet() HashSet(Collection<? extends E> c) HashSet(int capacity) HashSet(int capacity,float fillRatio) HashSet不能保证元素的顺序,注意这一点很重要,因为散列处理的过程通常不创建有序的组。如果需要有序的进行存储,那么需要另外一个组,TreeSet是一个比较好的选择。 下面是演示了HashSet的一个例子:
import java.util.*; class HashSetDemo { public static void main(String[] args) { HashSet<String> hs = new HashSet<String>(); hs.add("Beta"); hs.add("Alpha"); ha.add("Eta"); System.out.println(hs); } }
输出: [Eta,Beta,Alpha] 正如前面所解释的,元素不是按有序的顺序存储的,精确的输出可能不同。
4. LinkedHashSet类
LinkHashSet类扩展了HashSet类,它没有添加自己的方法。LinkedHashSet是泛型类,其声明如下: class LinkedHashSet<E LinkedHashSet维护组中条目的一个链表。链表中条目的顺序也就是插入它们的顺序,这使得可以按照插入顺序迭代集合。换句话说,当使用迭代器遍历LinkedHashSet时,元素将以插入它们的顺序返回。
5. TreeSet类
TreeSet扩展了AbstractSet类并实现了NavigableSet接口,用于创建使用树进行存储的组。对象以升序存储,访问和检索速度相当快,这使得存储大量的、必须能够快速查找到的有序信息来说,TreeSet是极佳选择。 TreeSet是泛型类,其声明如下: class TreeSet<T> TreeSet具有如下构造函数: TreeSet() TreeSet(Collection<? extends E> c) TreeSet(Comparator<? super E> comp) TreeSet(SortedSet<E> ss) 第1种形式构建一个空树,将按照元素的自然顺序以升序进行存储。 第2种形式构建一个包含集合c中元素的树。 第3种形式构建了一个空树,将按照comp指定的比较器进行存储(比较器将在本章后面描述) 第4种形式构建一个包含ss中元素的树。 下面演示TreeSet类的一个例子:
class TreeSetDemo { public static void main(String[] args) { TreeSet<String> ts = new TreeSet<String>(); ts.add("C"); ts.add("A"); ts.add("B"); ts.add("E"); ts.add("F"); ts.add("D"); System.out.println(ts); } }
该程序输出: [A,B,C,D,E,F]
6. PriorityQueue类
PriorityQueue扩展了AbstractQueue类并实现了Queue接口,用于创建根据队列的比较器来判定优先次序的队列。 PriorityQueue是泛型类,其声明如下: class PriorityQueue<E> PriorityQueue是动态的、按需增长的。 PriorityQueue定义了一下7个构造函数: PriorityQueue() PriorityQueue(int capacity) PriorityQueue(Comparator<? super E> comp) (JDK 8新增) PriorityQueue(int capacity,Comparator<? super E> comp) PriorityQueue(Collection<? extends E> c) PriorityQueue(PriorityQueue<? extends E> c) PriorityQueue(SortedSet<? extends E> c) 第1个构造函数构建一个空队列,其实容量为11. 第2个构造函数构建一个具有指定初始容量的的队列。 第3个构造函数指定了一个比较器。 第4个构造函数构建具有指定容量和比较器的队列。 最后3个构造函数创建使用参数c传递过来的集合中的元素进行初始化的队列。 对于由这些构造函数创建的所有队列,当添加元素时,容量都会自动增长。 当创建PriorityQueue对象时,如果没有指定比较器,将使用队列中存储数据类型的默认比较器。默认比较器以升序对队列进行排序。但是,通过提供定制的比较器,可以指定不同的排序模式。
7. ArrayDeque类
ArrayDeque扩展了AbstractCollection类并实现了Deque接口,没有添加自己的方法。ArrayDeque是泛型类,其声明如下: class ArrayDeque<E> ArrayDeque定义了以下构造函数: ArrayDeque() ArrayDeque(int size) ArrayDeque(Collection<? extends E> c) 下面演示了ArrayDeque,这里使用它来创建一个堆栈:
import java.util.*; class ArrayDequeDemo { public static void main(String[] args) { ArrayDeque<String> ad = new ArrayDeque<>(); //Use an ArrayDeque like a stack ad.push("A"); ad.push("B"); ad.push("D"); ad.push("E"); ad.push("F"); System.out.print("Poping the stack: "); while(ad.peek() != null) { System.out.print(ad.pop()+" "); } System.out.println(); } }
输出: Poping the stack: F E D B A
8. EnumSet类
EnumSet扩展了AbstractSet类并实现了Set接口,专门用于枚举类型的元素。EnumSet是泛型类,其声明如下: class EnumSet<E extends EnumSet<E>> 其中,E指定了元素。注意E必须扩展Enum<E>,这强制要求元素必须是指定的枚举类型。
18.5 通过迭代器访问集合
迭代器是实现了Iterator或ListIterator接口的对象。Iterator接口允许遍历、获取或移除元素。ListIterator接口扩展了 Iterator接口,允许双向遍历列表,并且允许修改元素。Iterator和ListIterator是泛型接口,它们的声明如下: interface Iterator<E> interface ListIterator<E>
Iterator接口声明的方法:
void forEachRemainning(Consumer
import java.util.*; class IteratorDemo { //Create an array list. ArrayList<String> al = new ArrayList<String>(); //Add elements to the array list. al.add("C"); al.add("A"); al.add("E"); al.add("B"); al.add("D"); al.add("F"); //Use iterator to display contents of al. System.out.println("Original contents of al: "); Iterator<String> itr = al.iterator(); while(itr.hasNext()) { String element = itr.next(); System.out.print(element+" "); } System.out.println(); //Modify objects being iterated. ListIterator<String> litr = al.listIterator(); while(litr.hasNext()) { String element = litr.next(); litr.set(element+"+"); } System.out.print("Modified contents of al: "); itr = al.iterator(); while(itr.hasNext()) { String element = itr.next(); System.out.print(element+" "); } System.out.println(); //Now,display the list backwards. System.out.print("Modified list backwards: "); while(litr.hasPrevious()) { String element = litr.previous(); System.out.print(element+" "); } System.out.println(); }
输出: Original contents of al: C A E B D F Modified contents of al: C+ A+ E+ B+ D+ F+ Modified list backwards: F+ D+ B+ E+ A+ C+
18.5.2 使用for-each循环替代迭代器
如果不修改集合的内容,也不用反向获取元素,那么使用for-each风格的for循环遍历集合通常比使用迭代器更方便。请记住,可以使用for循环遍历任何实现了Iterator接口的集合对象。因为所有集合类都实现了这个接口。
18.6 Spliterator
JDK 8新增了一种叫做spliterator的迭代器,这种迭代器有Spliterator接口定义。Spliterator用于循环遍历元素序列,在 这一点上与刚才介绍过的迭代器类似。但是,使用spliterator的方法与使用迭代器的不同。另外,它提供的功能远比 Iterator或ListIterator多。可能对于Spliterator来说,最重要的一点是它支持并行迭代序列的一部分。Spliterator支持 并行编程。然后,即使用不到并行编程,也可以使用Spliterator。这么做的一个理由是它将hasNext()和next()操作合并到 一个方法中,从而提高效率。 Spliterator是一个泛型接口,其声明如下所示: interface Spliterator<T> Spliterator接口声明的几个下面用到的方法: default void forEachRemainning(Consumer<? super T> action):将action应用到数据源中未被处理的每一个元素。 boolean tryAdvance(Consumer<? super T> action): 在迭代中的下一个元素上执行action。如果有下一个元素,就返回true;否则返回false; 将Spliterator用于基本迭代任务十分简单:只需要调用tryAdvance()方法,直到其返回false。如果要为序列中的每个元素应用相同的动作,forEachRemainning()提供了一种高效的替代方法。对于这两个方法,在每次迭代中将发生的动作都有 Consumer对象对每个元素执行的操作定义。Consumer是一个函数式接口,向对象应用了一个动作。他是java.util.function 中声明的一个泛型接口。Consumer仅指定了一个抽象方法accept(),如下所示: void accept(R objRef) 对于tryAdvance(),每次迭代会将序列中的下一个元素传递个objRef。通常,实现Consumer最简单的方法是lambda表达式。 下面的程序给出了Spliterator的一个简单示例。注意,这个程序同时演示了tryAdvance()和forEachRemainning()。另外,还要注意这些方法如何把Iterator的next()和hasNext()方法操作到一个调用中:
import java.util.*; class SpliteratorDemo { public static void main(String[] args) { //Create an array list for doubles. ArrayList<Double> vals = new ArrayList<>(); vals.add(1.0); vals.add(2.0); vals.add(3.0); vals.add(4.0); vals.add(5.0); //Use tryAdvance() to display contents of vals. System.out.println("Contents of vals: "); Spliterator spltitr = vals.spliterator(); while(spltitr.tryAdvance((n) -> System.out.println(n))); System.out.println(); //Create new list that contains square roots. spltitr = vals.spliterator(); ArrayList<Double> sqrs = new ArrayList<>(); while(spltitr.tryAdvance((n) -> sqrs.add(Math.sqrt(n)))); //Use forEachRemainning() to display contents of sqrs. System.out.println("Contents of sqrs: "); spltitr = sqrs.spliterator(); spltitr.forEachRemainning((n) -> System.out.println(n)); System.out.println(); } }
输出: Contents of vals: 1.0 2.0 3.0 4.0 5.0 Contents of sqrs: 1.0 1.414... 1.732... 2.0 2.236... 虽然这个程序演示了Spliterator的原理,却没有展现其强大的能力。如前所述,在涉及并行处理的地方,Spliterator的最大优势才能体现出来。
18.9 使用映射
映射是存储键和值之间关联关系(键值对)的对象。给定一个键,就可以找到对应的值。键和值都是对象。键必须唯一,但是只可以重复。某些映射可以接受null键和null值,其他映射则不能。 关于映射需要注意的关键一点是:它们没有实现Iterator接口。这意味着不能使用for-each风格的for循环遍历映射。此外,不能为映射获取迭代器。但是,正如即将看到的,可以获取映射的集合视图,集合视图允许使用for循环和迭代器。
18.9.1 映射接口
支持映射的接口有Map、Map.Entry、NavigableMap和SortedMap。
1. Map接口
Map接口将唯一键映射到值。键是以后用于检索值的对象。给定键和值,可以在Map对象中存储值;存储值以后,可以使用相应的键检索值。Map是泛型接口,其声明如下: interface Map<K,V> 其中,K指定了键的类型,V指定了值的类型。 映射围绕两个基本方法:get()和put()。为了将值放入映射中,使用put()方法,指定键和值。为了获取值,调用get()方法,传递键作为参数,值会被返回。 尽管映射是集合框架的一部分,但映射不是集合,因为没有实现Collections接口。但是,可以获取映射的集合视图。为此,可以使用entrySet()方法。该方法返回一个包含映射中元素的Set对象。为了获取键的集合视图,使用keySet()方法;为了得到值的集合视图,使用values()方法。对于这3个集合视图,集合都是基于映射的。修改其中的一个集合会影响其他集合。集合视图是将映射集成到更大集合框架中的手段。
2. SortedMap接口
SortedMap接口扩展了Map接口,确保条目以键的升序保存。SortedMap是泛型接口,其声明如下: interface SortedMap<K,V>
3. NavigableMap接口
NavigableMap接口扩展了SortedMap接口,支持基于最接近匹配原则的条目的检索行为,即支持检索与给定的一个或多个键最相匹配的条目。NavigableMap是泛型接口,其声明如下: interface NavigableMap<K,V>
4. Map.Entry接口
Map.Entry接口提供了操作映射条目的功能。请记住,Map接口声明的entrySet()方法返回一个包含映射条目的Set对象。组的所有元素都是Map.Entry对象。Map.Entry是泛型接口,起声明如下: interface Map.Entry<K,V>
18.9.2 映射类
AbstractMap:实现了Map接口的大部分 HashMap:扩展了AbstractMap,以使用哈希表 TreeMap:扩展了AbstractMap,以使用树结构 LinkedHashMap:扩展了HashMap,以允许按照插入顺序进行迭代 EnumMap:扩展了AbstractMap,以使用enum键 WeekHashMap:扩展了AbstractMap,以使用带有弱键的哈希表 IdentityHashMap:扩展了AbstractMap,并且当比较文档时使用引用相等性 注意AbstractMap是所有具体映射实现的超类。
1. HashMap类
HashMap扩展了AbstractMap类并实现了Map接口。它使用哈希表存储映射,这使得即使对于比较大的集合,get()和put()方法的执行时间也保持不变。 应当注意哈希映射不保证元素的顺序。所以,向哈希映射添加元素的顺序不一定是通过迭代器读取它们的顺序。 下面的程序演示了HashMap,将名字映射的账户金额。注意获取和使用组视图的方式:
import java.util.*; class HashMapDemo { public static void main(String[] args) { HashMap<String,Double> hm = new HashMap<>(); hm.put("John Doe",new Double(3434.34)); hm.put("Tom Smith",new Double(123.22)); hm.put("Jane Baker",new Double(1378.00)); hm.put("Tod Hall",new Double(99.22)); hm.put("Ralph Smith",new Double(-19.08)); //Get a set of the entries. Set<Map.Entry<String,Double>> set = hm.entrySet(); //Display the set. for(Map.Entry<String,Double> me:set) { System.out.print(me.getKey()+";") System.out.println(me.getValue()); } System.out.println(); //Deposit 1000 into John Doe's account. double balance = hm.get("John Doe"); hm.put("John Doe",balance+1000); System.out.println("John Doe's new balance: "+hm.get("John Doe")); } }
输出(精确的顺序可能会有所变化): Ralph Smith: -19.08 Tom Smith: 123.22 John Doe: 3434.34 Tod Hall: 99.22 Jane Baker: 1378.0 John Doe's new balance: 4434.34
2. TreeMap类
TreeMap扩展了AbstractMap类并实现了NavigableMap接口,该类用于创建存储在树结构中的映射。TreeMap提供了有序存储键/值对的高效手段,并支持快速检索。应当注意,与哈希映射不同,树映射确保元素以键的升序存储。 下面的程序对前面的程序进行了修改,以使用TreeMap类:
import java.util.*; class TreeMapDemo { public static void main(String[] args) { TreeMap<String,Double> tm = new TreeMap<>(); tm.put("John Doe",new Double(3434.34)); tm.put("Tom Smith",new Double(123.22)); tm.put("Jane Baker",new Double(1378.00)); tm.put("Tod Hall",new Double(99.22)); tm.put("Ralph Smith",new Double(-19.08)); Set<Map.Entry<String,Double>> set = tm.entrySet(); for(Map.Entry<String,Double> me : set) { System.out.print(me.getKey()+":"); System.out.println(me.getValue()); } System.out.println(); //Deposit 1000 into John Doe's account. double balance = tm.get("John Doe"); tm.put("John Doe",balance+1000); System.out.println("John Doe's new balance: "+tm.get("John Doe")); } }
输出: Jane Baker: 1378.0 John Doe: 3434.34 Ralph Smith: -19.08 Tod Hall: 99.22 Tom Smith: 123.22 John Doe's new balance: 4434.34 注意TreeMap对键进行排序,然而这个例子中,它们根据名(first name)而不是姓(last name)进行排序。正如稍后描述的,在创建映射时,可以通过指定比较器来改变这种行为。
3. LinkedHashMap类
LinkedHashMap扩展了HashMap,在映射中以插入条目的顺序维护一个条目链表,从而可以按照插入顺序迭代整个映射。 也就是说,当遍历LinkedHashMap的集合视图时,将以元素的插入顺序返回元素。也可以创建按照最后访问的顺序返回元素的LinkedHashMa。
18.10 比较器
TreeSet和TreeMap类以有序顺序存储元素。然而,是比较器精确定义了"有序顺序"的含义。默认情况下,这些类使用Java称为"自然顺序"的方式对元素进行排序,自然顺序通常是你所期望的顺序(A在B之前,1在2之前,等等)。如果希望以不同的方式排序元素,可以在构造函组或映射时指定比较器,这样就可以精确控制在有序集合和映射中存储元素的方式。 Comparator是泛型接口,其声明如下: interface Comparator<T> 在JDK 8之前,Comparator接口只定义了两个方法:compare()和equals()。 compare()方法如下所示,用于比较两个元素以进行排序: int compare(T obj1,T obj2) obj1和obj2是要进行比较的对象。在正常情况下,如果对象相等,该方法返回0;如果obj1大于obj2,返回一个正值,反之,返回一个负值。如果要进行比较的对象的类型不兼容,该方法会抛出ClassCastException异常。通过实现compare()方法,可以改变对象的排序方式。例如,为了按照相反的顺序进行排序,可以创建比较器以反转比较的结果。 equals()方法如下所示,用于测试某个对象是否等于比较器: boolean equals(object obj) 其中,obj是将要进行相等测试的对象。如果obj和调用对象都是比较器,并且使用相同的排序规则,那么该方法返回true; 否则,返回false。不必重写equals()方法,并且大多数简单的比较器都不重写该方法。 在JDK 8后,通过使用默认接口方法和静态接口方法,JDK 8为Comparator添加了许多新功能。下面逐一进行介绍。 通过reserved()方法,可以获得一个比较器,该比较器颠倒了调用reserved()的比较器的排序: default Comparator<T> reserved() 该方法返回一个颠倒的比较器。 reserseOrder()是与reserve()关联的一个方法,如下所示: static <T extends Comparable<? super T>> Comparator<T> reserseOrder() 它返回一个颠倒元素的自然顺序的比较器。对应的,通过调用静态的naturalOrder()方法,可以获得一个使用自然顺序的比较器。该方法声明如下所示: static <T extends Comparable<? super T>> Comparator<T> natualOrder() 如果希望比较器能够处理null值,需要使用下面的nullsFirst()和nullsLast()方法: static <T> Comparator<T> nullsFirst(Comparator<? super T> comp) static <T> Comparator<T> nullsLast(Comparator<? super T> comp) nullsFirst()方法返回的比较器认为null比其他值小,nullLast()方法返回的比较器认为null比其他值大。对于这两个方法,如果被比较的两个值都是非null值,则comp执行比较。如果为comp赋值null,则认为所有非null值都是相等的。 JDK 8添加的另一个默认方法是thenComparing()。该方法返回一个比较器,当第一次比较的结果指出被比较的结果相等时, 返回的这个比较器将执行第二次比较。因此,可以使用该方法创建一个"根据X比较,然后根据Y比较"的序列。例如,当比较城市时,第一次可能比较城市名,第二次可能比较州名。如果城市名相同,则在比较州名。 thenComparing()方法有三种形式。 第一种形式如下所示:它允许通过传入Comparator的实例来指定第二个比较器。 default Comparator<T> thenComparing(Comparator<? super T> thenByComp) 其中,thenByComp指定在第一次比较返回相等后调用的比较器。 thenComparing()的另外两个版本允许指定标准函数式接口Function,如下所示: default <U extends Comparator<? super U>> Comparator<T> thenComparing(Function<? super T,? extends U> getKey) default <U extends Comparator<? super U>> Comparator<T> thenComparing(Function<? super T,? extends U> getKey,Comparator<? super U> keyComp) 在这两个版本中,getKey引用的函数用于获得下一个比较键,当第一次比较返回相等后,将使用这个比较键。后一个版本中的keyComp指定了用于比较键的比较器(U指定了键的类型)。
使用比较器:
下面演示了一个自定义比较器功能的例子。该例为字符串实现了compare()方法,以与正常顺序相反的顺序进行操作。因此,这将导致树组(tree set)以相反的顺序进行存储。
import java.util.*; class MyComp implements Comparator<String> { public int compare(String aStr,String bStr) { //Reverse the comparison return bStr.compareTo(aStr); } } class CompDemo { public static void main(String[] args) { TreeSet<String> ts = new TreeSet<>(new MyComp()); ts.add("C"); ts.add("A"); ts.add("B"); ts.add("E"); ts.add("F"); ts.add("D"); for(String element : ts) { System.out.print(element+" "); } System.out.println(); } }
输出: F E D C B A 尽管上面的程序实现逆序比较器的方法完全可以接受,但是从JDK8开始,还有另外一种方法可以获得解决方案。 即,现在可以简单的对自然顺序比较器调用reversed()方法。该方法返回一个等价的比较器,不过比较器的顺序是相反的。 例如,在前面的程序中,可以把MyComp重写为自然顺序的比较器,如下所示:
class MyComp implements Comparator<String> { public int compare(String aStr,String bStr) { return aStr.compareTo(bStr); } }
然后,可以使用下面的代码段创建一个反向排序字符串元素的TreeSet: MyComp mc = new MyComp(); TreeSet<String> ts = new TreeSet<>(mc.reserved()); 如果把这段新代码放到前面的程序中,得到的结果与原来的一样。在这个例子中,使用reversed()方法没有什么优势。但是,当需要同时创建自然顺序和反向顺序的比较器时,使用reversed()方法能够方便的获得逆序比较器,而不需要显示对其编码。 从JDK 8开始,在前面的例子中,实际上没有必要创建MyComp类,因为可以很方便的使用lambda表达式作为替代。 例如,可以彻底删除MyComp类,使用下面的代码创建字符串比较器: Comparator<String> mc = (aStr,bStr)->aStr.compareTo(bStr); 另外还有一点:在这个简单的示例中,通过使用lambda表达式,可以在对TreeSet()构造函数的调用中直接指定逆序比较器,如下所示: TreeSet<String> ts = new TreeSet<>((aStr,bStr) -> bStr.compareTo(aStr)); 做了上述修改后,程序得以显著缩短。下面显示了最终版本:
import java.util.*; class CompDemo2 { public static void main(String[] args) { TreeSet<String> ts = new TreeSet<>((aStr,bStr) -> bStr.compareTo(aStr)); ts.add("C"); ts.add("A"); ts.add("B"); ts.add("E"); ts.add("F"); ts.add("D"); for(String element : ts) { System.out.print(element+" "); } System.out.println(); } }
作为使用自定义比较器的更实际的例子,下面的程序是前面显示的存储账户余额的TreeMap程序的升级版。在前面的版本中,账户根据名字进行存储,但排序是从名开始的。下面的程序根据姓对账户进行排序。为此,使用比较器比较每个账户的姓,这会使得映射根据姓进行排序。
import java.util.*; class TComp implements Comparator<String> { public int compare(String aStr,String bStr) { int i,j,k; i = aStr.lastIndexOf(' '); j = bStr.lastIndexOf(' '); k = aStr.subString(i).compareToIgnoreCase(bStr.subString(j)); if(k == 0) //last name match,check entire name { return aStr.compareToIgnoreCase(bStr); } else { return k; } } } class TreeMapDemo2 { public static void main(String[] args) { TreeMap<String,Double> tm = new TreeMap<>(new TComp()); tm.put("John Doe",new Double(3434.34)); tm.put("Tom Smith",new Double(123.22)); tm.put("Jane Baker",new Double(1378.00)); tm.put("Tod Hall",new Double(99.22)); tm.put("Ralph Smith",new Double(-19.08)); Set<Map.Entry<String,Double>> set = tm.entrySet(); for(Map.Entry<String,Double> me : set) { System.out.print(me.getKey()+":"); System.out.println(me.getValue()); } System.out.println(); //Deposit 1000 into John Doe's account. double balance = tm.get("John Doe"); tm.put("John Doe",balance+1000); System.out.println("John Doe's new balance: "+tm.get("John Doe")); } }
输出: Jane Baker: 1378.0 John Doe: 3434.34 Tod Hall: 99.22 Ralph Smith: -19.08 Tom Smith: 123.22 John Doe's new balance: 4434.34 比较器类TComp比较包含名和姓的两个字符串。该类首先比较姓,如果姓相同的话,在根据名进行排序。 如果使用的是JDK 8或更高版本,还有另一种方法来编码前面的程序,让映射首先根据姓进行排序,然后根据名进行排序。 这会用到thenComparing()方法。回忆一下,thenComparing()允许指定第二个比较器,当调用比较器返回相等时,就会使用第二个比较器。下面的程序演示了这种方法,它修改了前面的程序,使之使用thenComparing()方法。
import java.util.*; //A comparator that compares last names. class CompLastNames implements Comparator<String> { public int compare(String aStr,String bStr) { int i,j; i = aStr.lastIndexOf(' '); j = bStr.lastIndexOf(' '); return aStr.subString(i).compareToIgnoreCase(bStr.subString(j)); } } //Sort by entire name when last names are equals. class CompThenByFirstName implements Comparator<String> { public int compare(String aStr,String bStr) { return aStr.compareToIgnoreCase(bStr); } } class TreeMapDemo2A { public static void main(String[] args) { CompLastNames compLN = new CompLastNames(); Comparator<String> compLastThenFirst = compLN.thenComparing(new CompThenByFirstName()); TreeMap<String,Double> tm = new TreeMap<>(compLastThenFirst); tm.put("John Doe",new Double(3434.34)); tm.put("Tom Smith",new Double(123.22)); tm.put("Jane Baker",new Double(1378.00)); tm.put("Tod Hall",new Double(99.22)); tm.put("Ralph Smith",new Double(-19.08)); Set<Map.Entry<String,Double>> set = tm.entrySet(); for(Map.Entry<String,Double> me : set) { System.out.print(me.getKey()+":"); System.out.println(me.getValue()); } System.out.println(); //Deposit 1000 into John Doe's account. double balance = tm.get("John Doe"); tm.put("John Doe",balance+1000); System.out.println("John Doe's new balance: "+tm.get("John Doe")); } }
这个版本产生的输入与原来的版本相同,二者唯一的区别在于完成的工作方法。 最后,注意为了清晰起见,本例显示创建了两个比较器类CompLastNames和ComThenByFirstName,但是其实也可以使用 lambda表达式。例如:
Comparator<String> compLastName = (aStr,bStr)-> { int i,j; i = aStr.lastIndexOf(' '); j = bStr.lastIndexOf(' '); return aStr.substring(i).compareToIgnoreCase(bStr.substring(j)); }; Comparator<String> compThenFirstName = (aStr,bStr)->aStr.compareToIgnoreCase(bStr); Comparator<String> comp = compLastName.thenComparing(compThenFirstName); TreeMap<String,Double> tm = new TreeMap<>(comp);
18.11 集合算法
集合框架定义了一些可以应用于集合和映射的算法,这些算法被定义为Collections类中的静态方法中,可以自行查看 Collections类的源码查看这些函数。 注意有些方法用于获取各种集合的同步(线程安全的)副本,例如synchronizedList()和synchronizedSet()。作为一般规则 ,标准集合实现不是同步的。必须使用同步算法提供同步。另外一点:同步集合的迭代器必须在synchronized代码块中使用 以unmodifiable开始的一组方法集返回各种不能修改的集合视图。如果希望确保对集合进行某些读取操作——但是不允许写入操作,这些方法是有用的。 下面的程序演示了一些算法,创建并初始化一个链表。reverseOrder()方法返回翻转Integer对象比较结果的比较器。列表元素根据这个比较器进行排序,然后显示。接下来,调用shuffle()方法以随机化链表,然后显示链表中的最大值和最小值
import java.util.*; class AlgorithmsDemo { public static void main(String[] args) { LinkedList<Integer> ll = new LinkedList<Integer>(); ll.add(-8); ll.add(20); ll.add(-20); ll.add(8); //Create a reserve order comparator. Comparator<Integer> r = Collections.reserseOrder(); //Sort list by using the comparator. Collections.sort(ll,r); System.out.print("List sorted in reverse: "); for(int i : ll) { System.out.print(i+" "); } System.out.println(); //Shuffle list. Collections.shuffle(ll); //Display randomized list. System.out.print(i+" "); System.out.println(); System.out.println("Mininum: "+Collections.min(ll)); System.out.println("Maxinum: "+Collections.max(ll)); } }
输出: List sorted in reserve: 20 8 -8 -20 List shuffled: 20 -20 8 -8 Mininum: -20 Maxinum: 20 注意是在随机化后,才对链表进行min()和max()操作的。这两个方法的操作都不要求列表是排序过的。
18.12 Arrays类
Arrays类提供了对数组操作有用的方法,这些方法有助于连接集合和数组。 asList()方法返回基于指定数组的列表。 static <T> List asList(T ...array) 其中,array是包含数据的数组。 binarySeach()方法使用二分搜索法查找特定数值。 copyOf()方法返回数组的副本。 copyOfRange()方法返回数组中某个范围的副本。 fill()方法可以将某个值赋给数组中的所有元素。 sort()方法用于对数组进行排序。 JDK 8为Arrays类添加了一些新的方法,其中最重要的可能是parallelSort()方法,因为该方法按升序对数组的各部分 进行并行排序,然后合并结果。这种方法可以显著加速排序时间。 通过包含spliterator()方法,JDK 8为Arrays类添加了对spliterator的支持。该方法有两种基本形似。 第一种版本返回整个数组的spliterator,如下所示: static<T> Spliterator spliterator(T array[]) 这里,array是spliterator将循环遍历的数组。第二种版本的spliterator()方法允许指定希望进行迭代的数组范围。 从JDK 8开始,通过包含stream()方法,Arrays类支持新的Stream接口。stream()方法有两个版本,下面显示了第一个 版本: static<T> Stream stream(T array[]) 这里,array是流将引用的数组。第二种版本的stream()方法允许指定数组内的一个范围。 除了刚才讨论的方法,JDK 8还添加了另外3个新方法。其中两个彼此相关:setAll()和parallelSetAll()。这两个方法 都是为所有元素赋值,但是parallelSetAll()以并行方式工作。 最后JDK 8还为Arrays类添加了一个有意思的方法:parallelPrefix().该方法对数组进行修改,使每个元素都包含对其 前面所有元素应用某个操作的累积结果。 下面演示了如何使用Arra类的一些方法:
import java.util.*; class ArraysDemo { public static void main(String[] args) { //Allocate and initialize array. int[] array = new int[10]; for(int i=0; i<10; i++) { array[i] = -3*i; } //Display,sort,and display the array. System.out.print("Original contents: "); display(array); Arrays.sort(array); System.out.print("Sorted: "); display(array); //Fill and display the array. Arrays.fill(array,2,6,-1); System.out.print("After fill(): "); display(array); //Sorted and display the array. Arrays.sort(array); System.out.println("After sorting again: "); display(array); //Binary seach for -9; System.out.print("The value -9 is at location: "); int index = Arrays.binarySeach(array,-9); System.out.println(index); } static void display(int[] array) { for(int i : array) { System.out.print(i+" "); } System.out.println(); } }
输出: Original contents: 0 -3 -6 -9 -12 -15 -18 -21 -24 -27 Sorted: -27 -24 -21 -18 -15 -12 -9 -6 -3 -0 After fill(): -27 -24 -1 -1 -1 -1 -9 -6 -3 0 After sorting again: -27 -24 -9 -6 -3 -1 -1 -1 -1 0 The value -9 is at location: 2
18.13 遗留的类和接口
在本章开头解释过,早期版本的java.util包没有包含集合框架,而是定义了几个类和一个接口,用来提供存储对象的专业方法。 本章介绍的所有现代集合类都不是同步的,但是所有遗留类都是同步的,有些情况这一差别很重要。当然,通过使用 Collections提供的算法,可以很容易的同步集合。 java.util定义的遗留类如下所示: Dictionary Hashtable Properies Stack Vector 还有遗留接口Enumeration. 此处,就介绍遗留类和接口的介绍了。
出自:《Java 8编程参考官方教程(第9版)》
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树
- [原创]java局域网聊天系统