本篇博文参考on Java8中文版编写
本编博文参考java编程思想第四版编写

文章目录

  • 概述
  • 一.泛型和类型安全的集合
  • 二.基本概念
  • 三.添加元素组(Adding Groups of Elements)
  • 四.集合的打印
  • 五.List列表
  • 六.迭代器Iterators
  • 七.ListIterator
  • 八.链表LinkedList
  • 九.栈Stack
  • 十.Set
  • 十一.Map(映射)
  • 十二.Queue(队列)
  • 十三.优先级队列PriorityQueue
  • 十四.集合与迭代器
  • 十五.for-in和迭代器
  • 十六.适配器方法惯用法
  • 十七.需要注意的问题!
  • 十八.总结
    • 18.1 简单集合分类法

概述

1.如果一个程序只包含固定数量的且其生命期都是已知的对象,那么这是一个非常简单的程序。通常,程序总是根据运行时才知道的某些条件去创建新的对象。在此之前,不会知道所需对象的数量甚至确切类型。为了解决这个普遍的编程问题,必须在任意时刻、任意位置创建任意数量的对象。因此,不能依靠创建命名的引用来持有每一个对象,因为你永远不会知道实际上需要多少个这样的引用:

MyType aReference;

2.Java有多种方式保存对象(确切地说,是对象的引用)。例如数组,它是编译器支持的类型。数组是保存一组对象的最有效的方式,如果想要保存一组基本类型数据,也推荐使用数组。但是数组具有固定的大小尺寸,而且在更一般的情况下,在写程序时并不知道将需要多少个对象,或者是否需要更复杂的方式来存储对象,因此数组尺寸固定这一限制就显得太过受限了。

3.java.util 库提供了一套相当完整的集合类(collection classes)来解决这个问题,其中基本的类型是 List 、Set 、Queue 和 Map。这些类型也被称作容器类(container classes)。集合提供了完善的方法来保存对象,可以使用这些工具来解决大量的问题。

一.泛型和类型安全的集合

1.使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个 Apple 对象的集合,这里使用 ArrayList 。现在,可以把 ArrayList 看作"可以自动扩充自身尺寸的数组"来看待。使用 ArrayList 相当简单:创建一个实例,用 add() 插入对象;然后用 get() 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。ArrayList 还有一个 size() 方法,告诉你有多少元素添加了进来,这样你就不会不小心因索引越界而引发错误(通过抛出运行时异常)。

2.下面这个例子中,Apple 和 Orange 都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 @SuppressWarnings 注解及其参数表示只抑制“unchecked”类型的警告:

class Apple {private static long counter;private final long id = counter++;public long id() {return id;}
}class Orange {}public class ApplesAndOrangesWithoutGenerics {@SuppressWarnings("unchecked")public static void main(String[] args) {ArrayList apples = new ArrayList();for (int i = 0; i < 3; i++) {apples.add(new Apple());}// No problem adding an Orange to apples:apples.add(new Orange());for (Object apple : apples) {((Apple) apple).id();// Orange is detected only at runtime}}
}
/* Output:
___[ Error Output ]___
Exception in thread "main"
java.lang.ClassCastException: Orange cannot be cast to Appleat ApplesAndOrangesWithoutGenerics.main(ApplesAndOrangesWithoutGenerics.java:23)
*/

3.Apple 和 Orange 类是不同的,它们除了都是 Object 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 Object)。因为 ArrayList 保存的是 Object ,所以不仅可以通过 ArrayList 的 add() 方法将 Apple 对象放入这个集合,还可以添加 Orange 对象,而且无论在编译期还是运行时都不会有问题。当使用 ArrayList 的 get() 方法来取出你认为是 Apple 的对象时,得到的只是 Object 引用,必须将其转型为 Apple。因此需要将整个表达式用括号括起来,以便在调用 Apple 的 id() 方法之前,强制执行转型。否则,将会产生语法错误。在运行时,当尝试将 Orange 对象转为 Apple 时,会出现输出中显示的错误。

4.要定义一个用于保存 Apple 对象的 ArrayList ,只需要使用 ArrayList <Apple 来代替 ArrayList ,它指定了这个集合实例可以保存的类型。通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。在 apples 定义的右侧,可以看到 new ArrayList<>() ,这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明–Java语言团队类型推断改进,所以编译器没有理由强迫我们在右边重复。

public class ApplesAndOrangesWithGenerics {public static void main(String[] args) {ArrayList<Apple> apples = new ArrayList<>();for (int i = 0; i < 3; i++) {apples.add(new Apple());}// Compile-time error:// apples.add(new Orange());for (Apple apple : apples) {System.out.println(apple.id());}}
}

5.使用泛型,从 List 中获取元素不需要强制类型转换。因为 List 知道它保存的是什么类型,因此当调用 get() 时,它会替你执行转型。因此,使用泛型,你不仅知道编译器将检查放入集合的对象类型,而且在使用集合中的对象时也可以获得更清晰的语法。当你指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以作用于泛型。因此,可以将 Apple 的子类型添加到被指定为保存 Apple 对象的集合中。程序的输出是从 Object 默认的 toString() 方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 hashCode() 方法产生的)。

class GrannySmith extends Apple {}
class Gala extends Apple {}
class Fuji extends Apple {}
class Braeburn extends Apple {}public class GenericsAndUpcasting {public static void main(String[] args) {ArrayList<Apple> apples = new ArrayList<>();apples.add(new GrannySmith());apples.add(new Gala());apples.add(new Fuji());apples.add(new Braeburn());for(Apple apple : apples) {System.out.println(apple);}}
}
/* Output:
GrannySmith@15db9742
Gala@6d06d69c
Fuji@7852e922
Braeburn@4e25154f
*/

二.基本概念

1.Java集合类库(The Java collection library)采用了“持有对象”(holding objects)的思想,并将其分为两个不同的概念,表示为类库的基本接口:

  1. 集合(Collection) :一个独立元素的序列,这些元素都服从一条或多条规则。List 必须以插入的顺序保存元素,Set 不能有重复元素, Queue 按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
  2. 映射(Map) : 一组成对的“键值对”对象,允许使用键来查找值。 ArrayList 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 map 允许我们使用一个对象来查找另一个对象,它也被称作关联数组(associative array),因为它将对象和其它对象关联在一起;或者称作字典(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 Map 是强大的编程工具。

2.在理想情况下,你编写的大部分代码都在与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,可以像下面这样创建一个 List ,请注意,ArrayList 已经被向上转型为了 List 。使用接口的目的是如果想要改变具体实现,只需在创建时修改它就行了。因此,应该创建一个具体类的对象,将其向上转型为对应的接口,然后在其余代码中都使用这个接口。这种方式并非总是有效的,因为某些具体类有额外的功能。例如, LinkedList 具有 List 接口中未包含的额外方法,而 TreeMap 也具有在 Map 接口中未包含的方法。如果需要使用这些方法,就不能将它们向上转型为更通用的接口。

List<Apple> apples = new ArrayList<Apple>();
List<Apple> apples = new LinkedList<Apple>();

3.Collection 接口概括了序列(sequence)的概念——一种存放一组对象的方式。下面是个简单的示例,用 Integer 对象填充了一个 Collection (这里用 ArrayList 表示),然后打印集合中的每个元素。这个例子仅使用了 Collection 中的方法,所以任何继承自 Collection 类的对象都可以正常工作,但是 ArrayList 是最基本的序列类型。Collection 接口中 boolean add(E e) API文档:如果调用的结果改变了集合则返回true(如果该集合不允许重复且已经包含指定的元素则返回false),这是因为考虑到了 Set 的含义,因为在 Set中只有当元素不存在时才会添加元素。而 List 不关心是否存在重复元素。可以使用 for-in 语法来遍历所有的 Collection 。

public class SimpleCollection {public static void main(String[] args) {Collection<Integer> c = new ArrayList<>();for (int i = 0; i < 10; i++) {c.add(i); // Autoboxing}for (Integer i : c) {System.out.print(i + ", ");}}
}

三.添加元素组(Adding Groups of Elements)

1.在 java.util 包中的 Arrays 和 Collections 类中都有很多实用的方法,可以在一个 Collection 中添加一组元素。Arrays.asList() 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 List 对象。 Collections.addAll() 方法接受一个 Collection 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 Collection 中。如下示例:

public class AddingGroups {public static void main(String[] args) {//Collection 的构造器可以接受另一个 Collection,用它来将自身初始化,因此可以使用 Arrays.asList() 来为这个构造器产生输入。Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));Integer[] moreInts = { 6, 7, 8, 9, 10 };//Collection.addAll() 方法只能接受另一个 Collection 作为参数,因此它没有 Arrays.asList() 或 Collections.addAll() 灵活,这两个方法都使用可变参数列表。collection.addAll(Arrays.asList(moreInts));// Runs significantly faster, but you can't construct a Collection this way:Collections.addAll(collection, 11, 12, 13, 14, 15);Collections.addAll(collection, moreInts);// Produces a list "backed by" an array:List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);list.set(1, 99); // OK -- modify an element//list.add(21); // Runtime error; the underlying array cannot be resized.}
}

2.可以直接使用 Arrays.asList() 的输出作为一个 List ,但是这里的底层实现是数组,没法调整大小,如果尝试在这个 List 上调用 add() 或 remove(),由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation”错误。在 snow 中,注意 Arrays.asList() 中间的“暗示”(即 <Snow ),告诉编译器 Arrays.asList() 生成的结果 List 类型的实际目标类型是什么。这称为显式类型参数说明(explicit type argument specification)。

class Snow {}
class Powder extends Snow {}
class Light extends Powder {}
class Heavy extends Powder {}
class Slush extends Snow {}public class AsListInference {public static void main(String[] args) {// Hint with explicit type argument specification:List<Snow> snow = Arrays.<Snow>asList(new Light(), new Heavy(), new Slush());
//        snow.add(new Powder()); // Exception
//        snow.remove(0);         // ExceptionList<Snow> snow2 = Arrays.asList(new Light(), new Heavy(), new Slush());}
}

四.集合的打印

1.必须使用 Arrays.toString() 来生成数组的可打印表示。但是打印集合无需任何帮助。下面的代码示例显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 Collection 类型在每个槽中只能保存一个元素。此类集合包括: List ,它以特定的顺序保存一组元素;Set ,其中元素不允许重复;Queue ,只能在集合一端插入对象,并从另一端移除对象。 Map 在每个槽中存放了两个元素,即键和与之关联的值。默认的打印行为,使用集合提供的 toString() 方法即可生成可读性很好的结果。 Collection 打印出的内容用方括号括住,每个元素由逗号分隔。 Map 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。

public class PrintingCollections {static Collection fill(Collection<String> collection) {collection.add("rat");collection.add("cat");collection.add("dog");collection.add("dog");return collection;}static Map fill(Map<String, String> map) {map.put("rat", "Fuzzy");map.put("cat", "Rags");map.put("dog", "Bosco");map.put("dog", "Spot");return map;}public static void main(String[] args) {System.out.println(fill(new ArrayList<>()));System.out.println(fill(new LinkedList<>()));System.out.println(fill(new HashSet<>()));System.out.println(fill(new TreeSet<>()));System.out.println(fill(new LinkedHashSet<>()));System.out.println(fill(new HashMap<>()));System.out.println(fill(new TreeMap<>()));System.out.println(fill(new LinkedHashMap<>()));}
}
/* Output:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[rat, cat, dog]
[cat, dog, rat]
[rat, cat, dog]
{rat=Fuzzy, cat=Rags, dog=Spot}
{cat=Rags, dog=Spot, rat=Fuzzy}
{rat=Fuzzy, cat=Rags, dog=Spot}
*/

2.HashSet 、TreeSet 和 LinkedHashSet 是 Set 的类型。Set 只保存每个相同项中的一个,并且不同的 Set 实现存储元素的方式也不同。 HashSet 使用相当复杂的方法存储元素,这在附录:集合主题中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此存储顺序看上去没有什么意义(通常只关心某事物是否是 Set 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 TreeSet ,它将按比较结果的升序保存对象;或 LinkedHashSet ,它按照被添加的顺序保存对象。

3.请注意,不必指定(或考虑) Map 的大小,因为它会自动调整大小。对于每个键, Map 只存储一次。本例使用了 Map 的三种基本风格: HashMap ,TreeMap 和 LinkedHashMap 。键和值在HashMap中的顺序不是插入顺序,因为HashMap实现使用了一种非常快速的算法来控制顺序。 TreeMap保持键按升序比较顺序排序,LinkedHashMap保持键按插入顺序排序同时保持HashMap的查找速度。

五.List列表

1.Lists承诺按特定顺序维护元素。 List 接口在 Collection 的基础上添加了许多方法,允许在 List 的中间插入和删除元素。

2.有两种类型的 List :

  • 基本的 ArrayList ,擅长随机访问元素,但在 List 中间插入和删除元素时速度较慢。
  • LinkedList ,在列表中间有代价较低(inexpensive)的插入和删除。 LinkedList 在随机访问方面相对较慢。

3.与数组不同,列表可以在创建后添加或删除元素,并调整自身大小。这就是它的基本价值:一个可修改的序列。

六.迭代器Iterators

1.如果从更高层次的角度考虑,会发现这里有个缺点:要使用集合,必须对集合的确切类型编程。这一开始可能看起来不是很糟糕,但是考虑下面的情况:如果原本是对 List 编码的,但是后来发现如果能够将相同的代码应用于 Set 会更方便,此时应该怎么做?或者假设想从一开始就编写一段通用代码,它不知道或不关心它正在使用什么类型的集合,因此它可以用于不同类型的集合,那么如何才能不重写代码就可以应用于不同类型的集合?

2.迭代器(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为轻量级对象(lightweight object):创建它的代价小。

3.Iterator接口如下。通过反复调用 next 方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末尾,next 方法将抛出一个 NoSuchElementException。因此需要在调用 next 之前调用 hasNext方法。如果迭代器对象还有多个供访问的元素, 这个方法就返回 true。如果想要査看集合中的所有元素,就请求一个迭代器,并在 hasNext 返回 true 时反复地调用 next 方法。Iterator 接口的 remove 方法将会删除上次调用 next 方法时返回的元素。对 next 方法和 remove 方法的调用具有互相依赖性。如果调用 remove 之前没有调用 next 将是不合法的。如果这样做,将会抛出一个 IllegalStateException 异常。

public interface Iterator<E> {boolean hasNext();E next();default void remove() {throw new UnsupportedOperationException("remove");}//对{@code Iterable}的每个元素执行给定的操作,直到所有元素都已处理或该操作引发异常。除非实现类另有规定,否则操作将按迭代顺序执行(如果指定了迭代顺序)。操作引发的异常将转发给调用方。default void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);while (hasNext())action.accept(next());}
}

4.用"foreach"循环可以更加简练地表示同样的循环操作。编译器简单地将"foreach"循环翻译为带有迭代器的循环。"for each"循环可以与任何实现了 Iterable 接口的对象一起工作。Collection 接口扩展了 Iterable 接口。

        Collection<String> c = xxx;Iterator<String> iterator = c.iterator();while (iterator.hasNext()) {String element = iterator.next();System.out.println(element);}
     for (String element : c) {System.out.println(element);}

5.现在考虑创建一个 display() 方法,它不必知晓集合的确切类型:

class Pet implements Comparable<Pet> {private int id;private String name;public Pet(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "Pet{" +"id='" + id + '\'' +", name='" + name + '\'' +'}';}@Overridepublic int compareTo(Pet o) {return Integer.compare(this.id, o.id);}@Overridepublic boolean equals(Object obj) {//检测obj是否为nullif (obj == null) {return false;}//检测this与obj是否引用同一个对象if (obj == this) {return true;}//getClass方法将返回一个对象所属的类,只有在两个对象属于同一个类时,才有可能相等。if (this.getClass() != obj.getClass()) {return false;}Pet pet = (Pet) obj;return Objects.equals(pet.name, this.name) && pet.id == this.id;}@Overridepublic int hashCode() {//返回一个散列码,由提供的所有对象的散列码组合而得到。return Objects.hash(id, name);}
}public class CrossCollectionIteration {public static <T> void display(Iterator<T> it) {while (it.hasNext()) {T t = it.next();System.out.print(t + " ");}System.out.println();}public static void main(String[] args) {List<Pet> pets = Arrays.asList(new Pet(2, "狗"),new Pet(1, "猫"),new Pet(4, "虎"),new Pet(3, "熊"),new Pet(1, "猫"));LinkedList<Pet> petsLL = new LinkedList<>(pets);HashSet<Pet> petsHS = new HashSet<>(pets);TreeSet<Pet> petsTS = new TreeSet<>(pets);display(pets.iterator());display(petsLL.iterator());display(petsHS.iterator());display(petsTS.iterator());}
}
/* Output:
Pet{id='2', name='狗'} Pet{id='1', name='猫'} Pet{id='4', name='虎'} Pet{id='3', name='熊'} Pet{id='1', name='猫'}
Pet{id='2', name='狗'} Pet{id='1', name='猫'} Pet{id='4', name='虎'} Pet{id='3', name='熊'} Pet{id='1', name='猫'}
Pet{id='2', name='狗'} Pet{id='3', name='熊'} Pet{id='1', name='猫'} Pet{id='4', name='虎'}
Pet{id='1', name='猫'} Pet{id='2', name='狗'} Pet{id='3', name='熊'} Pet{id='4', name='虎'}
*/

七.ListIterator

1.ListIterator 是一个更强大的 Iterator 子类型,它只能由 List 类生成。 Iterator 只能向前移动,而 ListIterator 可以双向移动。它可以生成相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用 set() 方法替换它访问过的最近一个元素。可以通过调用 listIterator() 方法生成指向 List 开头的 ListIterator ,还可以通过调用 listIterator(n) 创建一个一开始就指向列表索引为 n 的元素处的 ListIterator 。 下面的示例演示了所有这些能力:

     //pets参见上面的代码...此处省略ListIterator<Pet> it = pets.listIterator();while (it.hasNext()) {System.out.print(it.next() + ", " + it.nextIndex() + ", " + it.previousIndex() + "; ");}System.out.println();//Backwards:while (it.hasPrevious()) {System.out.print(it.previous() + " ");}System.out.println();System.out.println(pets);it = pets.listIterator(2);while (it.hasNext()) {it.next();it.set(new Pet(5, "狮子"));}System.out.println(pets);
/* Output:
Pet{id='2', name='狗'}, 1, 0; Pet{id='1', name='猫'}, 2, 1; Pet{id='4', name='虎'}, 3, 2; Pet{id='3', name='熊'}, 4, 3; Pet{id='1', name='猫'}, 5, 4;
Pet{id='1', name='猫'} Pet{id='3', name='熊'} Pet{id='4', name='虎'} Pet{id='1', name='猫'} Pet{id='2', name='狗'}
[Pet{id='2', name='狗'}, Pet{id='1', name='猫'}, Pet{id='4', name='虎'}, Pet{id='3', name='熊'}, Pet{id='1', name='猫'}]
[Pet{id='2', name='狗'}, Pet{id='1', name='猫'}, Pet{id='5', name='狮子'}, Pet{id='5', name='狮子'}, Pet{id='5', name='狮子'}]
*/

八.链表LinkedList

1.LinkedList 也像 ArrayList 一样实现了基本的 List 接口,但是它比 ArrayList 更高效地在 List 中间执行插入和删除操作。然而,对于随机访问操作(random-access)它的效率较低。

2.LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque) 。

  • getFirst() 和 element() 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 List 为空,则抛出NoSuchElementException 异常。 peek() 方法与这两个方法只是稍有差异,它在列表为空时返回 null 。
  • removeFirst() 和 remove() 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 NoSuchElementException 异常。 poll() 稍有差异,它在列表为空时返回 null 。
  • addFirst() 在列表的开头插入一个元素。
  • offer() 与 add() 和 addLast() 相同。 它们都在列表的尾部(末尾)添加一个元素。
  • removeLast() 删除并返回列表的最后一个元素。
        LinkedList<Pet> pets = new LinkedList<>(Arrays.asList(new Pet(2, "狗"),new Pet(1, "猫"),new Pet(4, "虎"),new Pet(3, "熊"),new Pet(1, "猫")));System.out.println(pets);// Identical(完全相同):System.out.println("pets.getFirst(): " + pets.getFirst());System.out.println("pets.element(): " + pets.element());// Only differs in empty-list behavior(只有空列表行为不同):System.out.println("pets.peek(): " + pets.peek());// Identical; remove and return the first element(完全相同;移除并返回第一个元素):System.out.println("pets.remove(): " + pets.remove());System.out.println("pets.removeFirst(): " + pets.removeFirst());// Only differs in empty-list behavior(只有空列表行为不同):System.out.println("pets.poll(): " + pets.poll());System.out.println(pets);pets.addFirst(new Pet(5, "巴西龟"));System.out.println("After addFirst(): " + pets);pets.offer(new Pet(6, "布偶"));System.out.println("After offer(): " + pets);pets.add(new Pet(7, "金毛"));System.out.println("After add(): " + pets);pets.addLast(new Pet(8, "柴犬"));System.out.println("After addLast(): " + pets);System.out.println("pets.removeLast(): " + pets.removeLast());

九.栈Stack

1.堆栈是“后进先出”(LIFO)集合。它有时被称为叠加栈(pushdown stack),因为最后“压入”(push)栈的元素,第一个被“弹出”(pop)栈。经常用来类比栈的事物是带有弹簧支架的自助餐厅托盘。最后装入的托盘总是最先拿出来使用的。Java 1.0 中附带了一个 Stack 类,结果设计得很糟糕(为了向后兼容,我们永远被Java中的旧设计错误所困扰)。Java 6 添加了 ArrayDeque ,它包含直接实现堆栈功能的方法:

public class StackTest {public static void main(String[] args) {Deque<String> stack = new ArrayDeque<>();for (String s : "My dog has fleas".split(" ")) {stack.push(s);}while (!stack.isEmpty())System.out.print(stack.pop() + " ");}
}

2.ArrayDeque 包含直接实现堆栈功能的方法,我们仍然必须将其声明为 Deque 。有时一个名为 Stack 的类更能把事情讲清楚。Stack 是使用 ArrayDeque 实现的。如果你只需要栈的行为,,那么继承在这里是不合适的,因为这将产生一个具有 ArrayDeque 的其它所有方法的类(Java 1.0 设计者在创建 java.util.Stack 时,就犯了这个错误)。

// A Stack class built with an ArrayDeque
public class Stack<T> {private Deque<T> storage = new ArrayDeque<>();public void push(T v) { storage.push(v); }public T peek() { return storage.peek(); }public T pop() { return storage.pop(); }public boolean isEmpty() { return storage.isEmpty(); }@Overridepublic String toString() {return storage.toString();}public static void main(String[] args) {Stack<String> stack = new Stack<>();for (String s : "My dog has fleas".split(" ")) {stack.push(s);}while (!stack.isEmpty())System.out.print(stack.pop() + " ");}
}

十.Set

1.Set 不保存重复的元素。查找通常是 Set 最重要的操作,因此通常会选择 HashSet 实现,该实现针对快速查找进行了优化。

2.Set 具有与 Collection 相同的接口,因此不像两种不同类型的List那样有任何额外的功能。相反,Set 就是一个 Collection ,它只是有不同的行为(这是继承和多态思想的典型应用:表现不同的行为)。

3.HashSet使用散列(hashing)来提高速度——散列在附录:集合主题一章中有介绍。HashSet 维护的顺序与 TreeSet 或 LinkedHashSet 不同,因为每个实现都有不同的存储元素的方式。TreeSet 将元素存储在红-黑树数据结构中,而 HashSet 使用散列函数。LinkedHashSet 也使用散列来提高查找速度,但似乎使用一个链表来保持元素的插入顺序。

十一.Map(映射)

1.将对象映射到其他对象的能力是解决编程问题的强大方法。例如,考虑一个程序,它被用来检查 Java 的 Random 类的随机性。理想情况下, Random 会产生完美的数字分布,但为了测试这一点,则需要生成大量的随机数,并计算落在各种范围内的数字个数。 Map 可以很容易地解决这个问题。在本例中,键是 Random 生成的数字,而值是该数字出现的次数:

public class Statistics {public static void main(String[] args) {Random rand = new Random(47);Map<Integer, Integer> m = new HashMap<>();for (int i = 0; i < 10000; i++) {// Produce a number between 0 and 20:int r = rand.nextInt(20);//自动包装机制将随机生成的 int 转换为可以与 HashMap 一起使用的 Integer 引用(集合不能使用基本类型)。如果键不在集合中,则 get() 返回 null (这意味着该数字第一次出现)。否则, get() 会为键生成与之关联的 Integer 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 Integer 的装箱和拆箱)。Integer freq = m.get(r);               // [1]m.put(r, freq == null ? 1 : freq + 1);}System.out.println(m);}
}

2.Map 与数组和其他的 Collection 一样,可以轻松地扩展到多个维度,you make a Map whose values are Maps (and the values of those Maps can be other collections, even other Maps). 。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 Map<Person, List< Pet>> 即可。

3.Map 可以返回由其键组成的 Set ,由其值组成的 Collection ,或者由其键值对组成的 Set 。

十二.Queue(队列)

1.队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中尤其重要,因为它们可以安全地将对象从一个任务传输到另一个任务。

2.LinkedList 实现了 Queue 接口。 通过将 LinkedList 向上转型为 Queue ,Queue 接口限制了对 LinkedList 方法的访问,使得只有适当的方法是可用的(这里实际上可以将 Queue 强制转换回 LinkedList ,但至少我们不鼓励这样做)。

public class QueueDemo {public static void printQ(Queue queue) {while (queue.peek() != null) {System.out.print(queue.remove() + " ");}System.out.println();}public static void main(String[] args) {Queue<Character> qc = new LinkedList<>();for (char c : "Brontosaurus".toCharArray()) {qc.offer(c);}printQ(qc);}
}

十三.优先级队列PriorityQueue

1.优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。如果构建了一个消息系统,某些消息比其他消息更重要,不管它们何时到达,都应该尽早处理。在Java 5 中添加了 PriorityQueue ,以便自动实现这种行为。

2.当在 PriorityQueue 上调用 offer() 方法来插入一个对象时,该对象会在队列中被排序。默认的排序使用队列中对象的自然顺序(natural order),但是可以通过提供自己的 Comparator 来修改这个顺序。 PriorityQueue 确保在调用 peek() , poll() 或 remove() 方法时,获得的元素将是队列中优先级最高的元素。

十四.集合与迭代器

1.Collection 是所有序列集合共有的根接口。它可能会被认为是一个“附属接口”(incidental interface),即因为要表示其他若干个接口的共性而出现的接口。此外,java.util.AbstractCollection 类提供了 Collection 的默认实现,使得你可以创建 AbstractCollection 的子类型,而其中没有不必要的代码重复。

2.使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。因此,如果所编写的方法接受一个 Collection ,那么该方法可以应用于任何实现了 Collection 的类——这也就使得一个新类可以选择去实现 Collection 接口,以便该方法可以使用它。

3.但是当你需要实现一个不是 Collection 的外部类时,由于让它去实现 Collection 接口可能非常困难或麻烦,因此使用 Iterator 就会变得非常吸引人。

十五.for-in和迭代器

1.使用 for-in 是所有Collection对象的一个特征,之所以能够工作,是因为 Java 5 引入了一个名为 Iterable 的接口,该接口包含一个能够生成 Iterator 的 iterator() 方法。for-in 使用 Iterable 接口在序列中移动。因此,如果创建了任何实现了 Iterable 的类,都可以将它用于 for-in 语句中。

public class IterableClass implements Iterable<String> {protected String[] words = ("And that is how " + "we know the Earth to be banana-shaped.").split(" ");//iterator() 返回的是实现了 Iterator<String> 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词@Overridepublic Iterator<String> iterator() {return new Iterator<String>() {private int index = 0;@Overridepublic boolean hasNext() {return index < words.length;}@Overridepublic String next() {return words[index++];}@Overridepublic void remove() { // Not implementedthrow new UnsupportedOperationException();}};}public static void main(String[] args) {for(String s : new IterableClass())System.out.print(s + " ");}
}

2.for-in语句适用于数组或任何 Iterable ,但这并不意味着数组自动成为 Iterable,也不存在任何自动装箱。

十六.适配器方法惯用法

1.如果现有一个 Iterable 类,你想要添加一种或多种在 for-in 语句中使用这个类的方法,应该怎么做呢?例如,假设您想要选择是向前还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 iterator() 方法,则只能替换现有的方法,而不能实现遍历顺序的选择。

2.一种解决方案是所谓适配器方法(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为你必须提供一个特定的接口来满足 for-in 语句。当您有一个接口而需要另一个接口时,编写适配器可以解决问题。 在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 Iterable 对象的方法,该对象可以用于 for-in 语句。这使得我们可以提供多种使用 for-in 语句的方式:在 main 方法中,如果直接将 ral 对象放在 for-in 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 reversed() 方法,它会产生不同的行为。

class ReversibleArrayList<T> extends ArrayList<T> {ReversibleArrayList(Collection<T> c) {super(c);}public Iterable<T> reversed() {return new Iterable<T>() {public Iterator<T> iterator() {return new Iterator<T>() {int current = size() - 1;@Override public boolean hasNext() {return current > -1;}@Overridepublic T next() { return get(current--); }@Overridepublic void remove() { // Not implementedthrow new UnsupportedOperationException();}};}};}
}public class AdapterMethodIdiom {public static void main(String[] args) {ReversibleArrayList<String> ral =new ReversibleArrayList<>(Arrays.asList("To be or not to be".split(" ")));// Grabs the ordinary iterator via iterator():for(String s : ral){System.out.print(s + " ");}System.out.println();// Hand it the Iterable of your choicefor(String s : ral.reversed()){System.out.print(s + " ");}}
}

十七.需要注意的问题!

1.Arrays.asList(ia) 的输出被传递给了 ArrayList 的构造器,这将创建一个引用 ia 的元素的 ArrayList ,Collections.shuffle() 方法不会影响到原始数组,只是打乱了 list1 中的引用。但是,如果直接使用 Arrays.asList(ia) 的结果,这种打乱就会修改 ia 的顺序。重要的是要注意 Arrays.asList() 生成一个 List 对象,该对象使用底层数组作为其物理实现。如果执行的操作会修改这个 List ,并且不希望修改原始数组,那么就应该在另一个集合中创建一个副本。

public class ModifyingArraysAsList {public static void main(String[] args) {Random rand = new Random(47);Integer[] ia = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };List<Integer> list1 = new ArrayList<>(Arrays.asList(ia));System.out.println("Before shuffling: " + list1);Collections.shuffle(list1, rand);System.out.println("After shuffling: " + list1);System.out.println("array: " + Arrays.toString(ia));List<Integer> list2 = Arrays.asList(ia);System.out.println("Before shuffling: " + list2);Collections.shuffle(list2, rand);System.out.println("After shuffling: " + list2);System.out.println("array: " + Arrays.toString(ia));}
}


十八.总结

1.不要在新代码中使用遗留类 Vector ,Hashtable 和 Stack 。

2.Java集合的简化图

18.1 简单集合分类法

1.实际上只有四个基本的集合组件Map、List、Set和Queue,它们各有两到三个实现版本(Queue 的 java.util.concurrent 实现未包含在此图中)。最常使用的集合用黑色粗线框表示。虚线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的虚线表示特定的类实现了一个接口。实心箭头表示某个类可以生成箭头指向的类的对象。

OnJava8读书笔记(java编程思想)--集合Collections相关推荐

  1. 12.JAVA编程思想——集合的类型

    12.JAVA编程思想--集合的类型 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51100510 标准Java 1.0 和1 ...

  2. 12 JAVA编程思想——集合的类型

    12.JAVA编程思想--集合的类型 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51100510 标准Java 1.0 和1 ...

  3. java学习笔记 java编程思想 第7章 复用类

    文章目录 7.1 组合语法 练习1 7.2 继承语法 7.2.1 初始化基类 练习3 练习4 练习5 带参数的构造器 练习6 练习7 练习8 练习9 练习10 7.3 代理 练习11 7.4 结合使用 ...

  4. java学习笔记 java编程思想 第6章 访问权限控制

    文章目录 6.1 包:库单元(the library unit) 6.1.1 代码组织 6.1.2 创建独一无二的包名 练习1 练习2 6.1.3 定制工具类 6.1.4 用import改变行为 练习 ...

  5. java学习笔记 java编程思想 第4章 控制执行流程

    目录 4.1 true和false 4.2 if-else 4.3 迭代 4.3.1 do-while 4.3.2 for 练习1 练习2 练习3 练习4 练习5 4.3.3 逗号操作符 4.4 Fo ...

  6. 《Java编程思想》读书笔记

    前言:三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十七章到第十八章的内容,这一次 ...

  7. 《Java编程思想》读书笔记(二)

    三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第一章到第十章的内容,这一次记录的是第 ...

  8. 【java】《java编程思想》 读书笔记

    之前主要用的C++的比较多,之前花了快2个月的实际认真系统全面的学习了以下java的基础语法,<java编程思想>这本书翻译水平确实不是很好,很多话读着会比较拗口.推荐读之前,先去网上搜索 ...

  9. Java编程思想读书笔记(七)

    点击蓝字 关注我们 今天是端午节,在这里祝大家端午节安康.过节的同时也不要忘记知识储备,今天我 为大家带来了Java编程思想第七节多形性读书笔记.请大家一起品鉴,如果发现里面有啥写的不对的地方,请大家 ...

最新文章

  1. mysql5.6.37驱动_MySql (mysql-5.6.37) 在Windows的安装及使用
  2. python下什么-python要下什么
  3. spring boot三:spring boot的hello, world
  4. 常用Function Module
  5. 载:看了你能懂几个?
  6. UITabBarController使用总结
  7. xdoj判断堆栈出栈序列是否有效c++
  8. 烂泥:通过vsphere给esxi添加本地硬盘
  9. 虚构合同、虚开发票套取高校配套科研经费,一副教授被公诉!
  10. 大图幻灯粉红色bootstrap4引导页模板
  11. 手机玩html5游戏很卡,手机游戏卡怎么办_手机玩游戏卡顿解决办法-系统城
  12. console线驱动安装_文通证件识别SDK和驱动安装使用说明
  13. Javascript字符串长度返回错误的原因
  14. 安防的未来五年 如何把握机遇深耕市场?
  15. [!] Unable to satisfy the following requirements:
  16. 图解HTTP---------------------------------------------------3
  17. coreldraw铺花纹_Coreldraw绘图实例教程,教你cdr环形花边绘制方法
  18. 关于读书学习与思考力。 文/江湖一剑客
  19. 现代计算机图形学笔记(六)——布林-冯反射模型、图形管线、纹理映射
  20. 「津津乐道播客」#273 科技乱炖:实时音频社交爆红的冷思考

热门文章

  1. 计算机软件保护现状,计算机软件保护问题探讨
  2. [收藏]进退两难—一个项目经理的日记{作者john.shen}与四位专家的点评
  3. linux游戏设计的意义,《Linux环境下五子棋游戏设计》-毕业论文(设计).doc
  4. Ubuntu openKylin 安装open VMware tool 工具
  5. 世界首富的22个习惯
  6. wifi p2p本机设备名DeviceName设置
  7. 赚钱 36 计 - 第三十一计:微商计
  8. 2019最新千峰Php高级工程师视频教程
  9. 树莓派学习笔记(五)——烟雾浓度检测(ONENET云平台收发数据代码)
  10. LM2576S上电瞬间烧坏