java开发人员的收藏指南,不要说我没告诉你!

自Java编程语言诞生以来,已经发生了很多变化,但Java Collections Framework(JCF)从早期开始就一直是Java的主干。小编从事在线教育多年,将自己的资料整合建了一个QQ群,对于有兴趣一起交流学习java的可以加群:732976516,里面有大神会给予解答,也会有许多的资源可以供大家学习分享,欢迎大家前来一起学习进步!

虽然对JCF进行了许多改进,但最重要的是增加了泛型和并发性,JCF基本保持不变。在这段时间里,集合已经成为几乎每个Java应用程序中不可或缺的一部分,学习使用这些接口和类已经成为大多数有抱负的Java开发人员的需求。

 

一次沿着记忆的旅行

在Java早期,没有共同的收集框架; 可用的最好的是以数组,Vectors或Hashtables 形式的松散收集的数据结构。与当时许多流行的编程语言不同,Java并没有将这些不同的类型组合到一个具有共同祖先和通用特征的框架中。随着1994年在C ++中引入标准模板库(STL),Java在创建具有分层数据结构的通用框架方面落后了。

直到1998年底发布Java Development Kit(JDK)2,最流行的全功能集合框架是ObjectSpace 的 Generic Collection Library(JGL)和Doug Lea的Collections Package。使用JGL作为基础,Joshua Bloch(Effective Java的作者)系列)设计了我们今天所知的大部分JCF。事实上,许多集合类的作者标签至今仍然有他的名字。随着JDK 2的出现,Java引入了一个基本的集合框架,但随着JDK 5的发布,这个集合框架在2004年秋季得到了重大升级。这个Java版本引入了类型擦除的泛型,它将JCF转换为类型不安全框架(在检索元素时需要显式强制转换)到完整的通用框架。JDK 5还引入了JCF中的并发性(通过Java规范请求(JSR)166),由JCF的前辈Doug Lea带头。

从那时起,对JCF进行了各种升级,包括以Streams应用程序编程接口(API)的形式引入函数式编程概念,但JCF基本保持不变。由于它是Java框架中使用最广泛的一种,JCF的更新和改进一直是Java社区最关注的问题之一,即使是在Java的早期阶段。

 

集合的概念

在Java环境中,集合定义如下

集合表示一组对象,称为其元素

这个定义有意模糊。它没有说明如何对集合中的元素进行分组,是否可以随机访问这些元素,是否允许重复元素,或者是否对元素进行了排序。相反,Java只需要将元素添加到集合中,从集合中删除并迭代(不声明迭代顺序)。此外,集合必须提供对其当前状态的查询,例如它包含的当前元素数,集合是否为空,以及任何元素是否在集合中; 他们还必须为数组提供转换方法(以便与现有的基于数组的应用程序兼容)。通过 在JDK 8中添加Streams(API),集合还必须可以转换为其元素流。

根据对集合的描述,我们可以定义以下一组职责:

  • 查询元素数量

  • 查询集合是否为空

  • 查询集合中是否包含任意元素

  • 迭代元素

  • 生成一系列元素

  • 添加新元素

  • 删除现有元素

  • 删除所有现有元素

  • 生成其元素流

 

收集界面

正如所料,Java在Collection接口中捕获这些责任,接口使用正式的通用参数进行参数化E,该参数表示其元素的类型。JDK 9定义此接口如下:

 
 

public interface Collection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); default boolean removeIf(Predicate<? super E> filter) { /* ... */ } boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); default Spliterator<E> spliterator() { /* ... */ } default Stream<E> stream() { /* ... */ } default Stream<E> parallelStream() { /* ... */ } }

 

 

大小和isEmpty

在集合的白话中,当前包含在集合中的元素数量称为其大小。因此,为了查询集合中的元素数量,我们必须调用该size()方法。如果集合的大小为0,则该集合被视为空 (isEmpty() 返回true)。

 

包含

该 contains(Object o) 方法根据以下规则检查提供的对象是否包含在集合中:

一个对象 a,如果存在至少一个元件被包含在集合中 b的集合中,使得 a.equals(b)返回 true。If ais null,如果 a集合中至少存在一个null元素, 则包含在 集合中。

更正式地说:

a如果集合中存在b 满足三元表达式的某个元素,则 对象 包含在集合中 (a == null ? b == null : a.equals(b))

需要注意的是,这并不意味着它是重要 equals 的方法 a 实际上是调用。由实现决定如何测试相等性。可以根据集合的性质进行优化。例如,该 hashCode() 方法说明书指出,具有不同的两个对象 hashCode() 的值是通过定义不相等的,因此,一种实施方式可以有利于测试平等这个手段,而不是明确地执行 a.equals(b)。

 

迭代器

该 Collection 接口实际上不是集合层次结构的顶层:它扩展了 Iterable 接口,该接口定义了可以迭代的类型。Iterable 接口的JDK 9定义如下:

 
 

public interface Iterable<T> { Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } }

 

第一种方法 iterator()只返回一个 Iterator 对象(我们很快就会解决)。该 forEach 方法是一种简单的默认实现,允许对集合中的每个元素执行非null操作。此方法利用for-each循环(形式上称为增强for循环),这是一种语法优化,允许为任何可迭代对象压缩for循环:

 
 

class FooIterable implements Iterable<Foo> { /* ... */ } FooIterable fooIterable; for (Foo foo: fooIterable) { // Do something with "foo" }

 

这种风格有一些限制,例如在迭代过程中删除元素,我们很快就会看到。最后一个方法创建一个Spliterator,它可以对集合进行分区并促进对这些分区的迭代。Spliterators是一个复杂的主题,与集合框架中的并行性密切相关,因此本文未对其进行介绍。好奇的读者应该参考Spliterator文档Java 8:并行性快速入门和Spliterator获取更多信息。

Iterable 接口的主要目的是创建一个Iterator 对象。An Iterator 是Iterator模式中的主要接口,允许对集合进行迭代。Iterator 对于JDK 9 ,接口定义如下:

 
 

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

在Iterator模式之后,此接口中的主要方法是 hasNext() 方法, true 如果有更多要迭代的元素 next() 则返回该方法,该方法返回要迭代的下一个元素并在迭代器中前进当前元素(即确保下一次调用 next() 将生成集合的下一个元素,而不是无限的相同元素)。除了这些基本方法, Iterator 界面还包括两个重要方法: remove() 和 forEachRemaining(Consumer<? super E> action)。

该 remove()) 方法对于在迭代期间从集合中移除元素至关重要。通常,不允许使用增强的for循环遍历集合,并从for循环体中的集合中删除元素。这会导致ConcurrentModificationException被抛出。例如,以下结果为ConcurrentModificationException:

 
 

for (Foo foo: fooIterable) { fooIterable.remove(foo); }

 

相反, remove() 必须在 Iterator 对象上调用以在迭代该集合时从集合中删除元素。例如:

 
 

for(Iterator<Foo> i = fooIterable.iterator(); i.hasNext();) { Foo foo = i.next(); i.remove(); }

 

请注意, next() 必须在调用之前调用remove(),因为该 next() 方法会使迭代器中的当前元素前进。虽然的组合 next(),hasNext()和 remove() 方法涵盖了绝大多数,随着迭代器打交道时,开发者通常会使用的功能,有走在这条重要的话题更深入的无数巨大的资源。有关更多信息,请参阅Iterator文档Oracle Collection Interface文档

 

指定者

由于Java泛型的限制,集合到数组的转换是古怪的,并且提供了两种方法。第一个是toArray() 返回Objects 数组的简单 方法,它保存由相同规则排序的集合元素,这些规则控制通过与集合关联的迭代器获得的元素的顺序(即,是否为迭代器建立了排序规则返回者 iterator(),这些规则也控制此数组中元素的顺序)。此方法是非泛型方法,并且数组中元素的类型不反映元素的类型,如集合的形式泛型参数所指定。

第二种方法是 toArray(T[] a) 方法,它返回集合中元素的数组,但保留元素的类型。因此,此方法返回的数组是一个对象数组,其类型与集合的形式泛型参数相同。适当的类型擦除对于Java中的泛型,正式泛型参数的类型在运行时是不可访问的,因此,在运行时创建与元素类型相同的数组是不可行的。因此,调用者负责在运行时提供数组类型(以元素类型的数组的形式)。如果提供的数组的大小等于大于集合的大小,则提供的数组将使用集合的元素填充,并且直接在集合的最后一个元素之后的元素将设置为null(如果数组的长度)减去集合的大小大于或等于1)。如果提供的数组的长度小于集合的大小,则返回一个新数组,其长度与集合的大小和提供的数组的类型相匹配。

例如,对此参数化方法的调用类似于以下内容:

 
 

Collection<Foo> foos = someMethod(); Foo[] fooArray = foos.toArray(new Foo[0]);

 

通过预先分配与集合大小相同长度的数组来优化此调用可能很诱人。例如:

 
 

Collection<Foo> foos = someMethod(); Foo[] fooArray = foos.toArray(new Foo[foos.size()]);

如Joshua Bloch在Effective Java,3rd Edition(第248页)第55项中所述,应该避免这种预分配优化。有关这两种阵列预分配技术之间性能差异的定量分析,请参阅 古代智慧阵列

 

该 add(E e) 方法将一个元素添加到集合中,并true 在集合发生更改时返回 。留下实现来决定是否 e 可以接受。例如,某些实现可能不接受重复值(即,如果 contains(e) 是 true),而其他实现可能不接受 null 值。

 

去掉

remove(Object o) 如果集合中包含至少一个与提供的对象相等的元素,则该 方法从集合中删除一个元素。如果删除了一个元素,则返回此方法true。删除的相等规则与contains(Object o) 方法的相同 。

 

containsAll,addAll和removeAll

当且仅当 集合中包含所有元素时, 该 containsAll(Collection<?> c) 方法才返回 。同样,该 方法将所有元素添加 到集合中, 如果集合被更改(即至少执行了一次添加)则返回。请注意, 如果在 方法启动和完成之间修改了集合,则方法的行为 是不确定的 。最后, 删除所有共同的元素 , 如果集合被修改则返回 (即,如果至少删除了一个元素)。完成后,保证收集包含共同元素 。truecaddAll(Collection<?>c)ctrueaddAlladdAllremoveAll(Collection<?> c) ctruec

 

removeIf

在removeIf(Predicate<? super E> filter) 默认情况下执行删除,满足供应的谓词的所有元素。实际上,此方法过滤掉满足所提供谓词的任何元素,并true 在修改集合时返回 (即至少删除了一个元素)。该方法的实现如下:

 
 

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

 

在内部,此方法使用集合提供的迭代器来测试集合中的每个元素,删除满足提供的谓词的元素(如上面的迭代器部分所示)。

 

中的retainAll

该 retainAll(Collection<?> c) 方法删除与此不相同的集合的所有元素 c。这相当于集合的交集 c。例如,如果集合包含元素[1, 2, 2, 3, 4, 5, 5],并且包含的集合 [1, 2, 4, 6] 提供给 retainAll 方法,则原始集合将缩减为 [1, 2, 2, 4]。true 如果此方法修改集合(即,如果至少删除了一个元素),则返回值 。

 

明确

从集合中删除所有元素。完成此方法后,该集合被视为空。

 

equals和hashCode

该 equals(Object o) 和 hashCode() 方法反映那些所有子类 Object 中的Java类。定制equals 和 hashCode 方法的集合的实现 有义务遵循对类类文档Object 中的所有类实现的一般限制 。

 

spliterator

该Collection 接口将覆盖接口spliterator 提供的默认 实现,并将 Iterable 接口 返回的Spliterator 关联 替换为 与集合本身关联Iterator的 Iterable接口。此方法的默认实现如下:

 
 

default Spliterator<E> spliterator() { return Spliterators.spliterator(this, 0); }

有关Spliterators的更多信息,请参阅 Spliterator文档

 

stream和parallelStream

JDK 8的主要补充之一是包含Streams API。此API将函数编程语义引入Java集合,将集合视为可以映射,过滤,减少等的数据流。Collection 接口的基于流的方法的默认实现 如下:

 
 

default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true); }

 

与 Spliterators一样,流是一个非常复杂的主题,超出了本系列的范围。好奇的读者可以在Stream类文档中找到更多信息。

 

隐含规则

虽然大多数关于集合实现的规则在编译时使用语言结构进行检查,但某些规则是语言外的。虽然未在编译时检查 ,但集合的所有实现都应遵守以下规则:

  • 有一个no-args构造函数,用于创建一个空集合(实现类型)和一个构造函数,它接受 Collection 并创建所提供的副本 Collection (转换构造函数 或 复制构造函数)

  • 不支持的破坏性方法(即修改集合的方法)应抛出 UnsupportedOperationException if不支持; 例如,调用 add 或 remove 在不可变集合上应该导致UnsupportedOperationException 被抛出

  • 同步由每个实现确定

 

集合层次结构

JCF的功能和实用性不是来自单个Collection 接口,而是来自 构成框架的各种其他接口,抽象类和具体实现。在这些其他集合类型中,有三个在它们的优势中脱颖而出:(1)List,(2)Set和(3)Queue。列表是有序集合或元素序列; 集合是一个不允许重复的集合(并且最多允许一个 null 元素),镜像数学集合; 队列是一个专为处理而设计的集合,通常以一些前置或后置顺序对其元素进行排序,例如先进先出(FIFO),后进先出(LIFO),或通过自然排序建立通过比较器(与优先级队列一样)。

正如我们将在本系列的以下文章中看到的那样,这三个概念涵盖了大多数集合的用例。此层次结构如下图所示,绿色框表示接口,蓝色框表示抽象类,紫色框表示具体实现组。请注意,可以在Falkhausen上找到对集合层次结构的出色交互式描述。

 

java开发人员的收藏指南,不要说我没告诉你!

 

值得注意的是,通常,集合框架遵循其实现的特定模式,其中Collection 接口的每个具体实现都 从与其类型密切相关的抽象类(即具体列表类扩展AbstractList 抽象类)继承 ,其中turn扩展了 AbstractCollection 抽象类和与集合类型紧密相关的接口(即 AbstractList 抽象类扩展了 AbstractCollection 抽象类并实现了 List 接口)。这个密切相关的接口然后扩展 Collection 接口,确保与集合相关联的所有特征通常与具体集合类型相关联(即 List 接口扩展 Collection 接口)。

正如我们将在以后的文章中看到的,这些集合类型中的每一个都引入了它们自己的约束和限制,使它们能够更精确地定义集合的行为。例如,由于列表的有序性质,该 List 接口包括用于随机访问的方法。我们将在本系列的下一篇文章中介绍此功能以及列表的所有其他特性。