一、集合简介
集合(有时称为容器)只是将多个元素分组到单个单元中的对象。集合用于存储、检索、操作和传达聚合数据。通常,它们表示形成自然组的数据项,例如扑克手(纸牌集合)、邮件文件夹(字母集合)或电话簿(名称到电话号码的映射)。如果您使用过 Java 编程语言(或者几乎任何其他编程语言),那么您已经熟悉集合。
1、什么是集合框架?
集合框架是用于表示和操作集合的统一体系结构。所有集合框架都包含以下内容:
- 接口:这些是表示集合的抽象数据类型。接口允许独立于其表示形式的细节操作集合。在面向对象的语言中,接口通常形成层次结构。
- 实现:这些是集合接口的具体实现。从本质上讲,它们是可重用的数据结构。
- 算法:这些是对实现集合接口的对象执行有用计算(如搜索和排序)的方法。这些算法被称为多态的:也就是说,相同的方法可以用于相应集合接口的许多不同实现。从本质上讲,算法是可重用的功能。
除了Java集合框架之外,最著名的集合框架示例是C++标准模板库(STL)和Smalltalk的集合层次结构。从历史上看,集合框架一直非常复杂,这使它们以具有陡峭的学习曲线而闻名。我们相信 Java 集合框架打破了这一传统,您将在本章中亲自学习。
2、Java 集合框架的优势
Java 集合框架具有以下优点:
- 减少编程工作量:通过提供有用的数据结构和算法,集合框架使您可以专注于程序的重要部分,而不是使其工作所需的低级“管道”。通过促进不相关的 API 之间的互操作性,Java 集合框架使您无需编写适配器对象或转换代码来连接 API。
- 提高程序速度和质量:此集合框架提供了有用数据结构和算法的高性能、高质量实现。每个接口的各种实现是可互换的,因此可以通过切换集合实现轻松调整程序。因为您摆脱了编写自己的数据结构的苦差事,所以您将有更多时间致力于提高程序的质量和性能。
- 允许不相关的 API 之间的互操作性:集合接口是 API 来回传递集合的方言。如果我的网络管理 API 提供节点名称的集合,并且您的 GUI 工具包需要列标题的集合,则我们的 API 将无缝互操作,即使它们是独立编写的。
- 减少学习和使用新 API 的工作量:许多 API 自然地将集合作为输出提供。过去,每个这样的 API 都有一个专门用于操作其集合的小型子 API。这些临时集合子 API 之间几乎没有一致性,因此您必须从头开始学习每个子 API,并且在使用它们时很容易出错。随着标准集合接口的出现,问题消失了。
- 减少设计新 API 的工作量:这是先前优势的另一面。设计人员和实现者不必在每次创建依赖于集合的 API 时都重新发明轮子;相反,它们可以使用标准集合接口。
- 促进软件重用:符合标准集合接口的新数据结构本质上是可重用的。对实现这些接口的对象进行操作的新算法也是如此。
二、接口
核心集合接口封装了不同类型的集合,如下图所示。这些接口允许独立于其表示形式的细节操作集合。核心集合接口是 Java 集合框架的基础。如下图所示,核心集合接口形成层次结构。
集合是一种特殊的集合,SortedSet是一种特别的集合,依此类推。还要注意,层次结构由两个不同的树组成——Map不是真正的Collection。
请注意,所有核心集合接口都是通用的。例如,这是Collection接口的声明。
public interface Collection<E>...
<E>语法告诉接口是通用的。当您声明集合实例时,您可以并且应该指定集合中包含的对象的类型。通过指定类型,编译器可以(在编译时)验证放入集合中的对象的类型是否正确,从而减少运行时的错误。
当您了解如何使用这些接口时,您将了解关于Java Collections Framework的大部分内容。本章讨论了有效使用接口的一般准则,包括何时使用哪个接口。您还将学习每个接口的编程习惯用法,以帮助您充分利用它。
为了保持核心集合接口的数量可管理,Java平台没有为每个集合类型的每个变体提供单独的接口。(这样的变体可能包括不可变、固定大小和仅追加。)相反,每个接口中的修改操作都被指定为可选的——给定的实现可能会选择不支持所有操作。如果调用了不支持的操作,则集合将抛出UnsupportedOperationException。实施负责记录他们支持的可选操作。Java平台的所有通用实现都支持所有可选操作。
以下列表介绍了核心集合接口:
- 集合-集合层次结构的根。集合表示一组称为其元素的对象。Collection接口是所有集合实现的最基本的接口,用于传递集合,并在需要最大通用性时对其进行操作。某些类型的集合允许重复元素,而另一些则不允许。有些是有序的,有些是无序的。Java平台不提供该接口的任何直接实现,但提供了更具体的子接口的实现,如Set和List。
- 集合-不能包含重复元素的集合。该接口对数学集合抽象进行建模,并用于表示集合,例如组成扑克手的卡片、组成学生时间表的课程或机器上运行的过程。另请参见“设置接口”部分。
- 列表-有序集合(有时称为序列)。列表可以包含重复的元素。列表的用户通常可以精确控制每个元素在列表中的插入位置,并可以通过其整数索引(位置)访问元素。如果您使用过Vector,那么您就熟悉List的一般风格。另请参阅“列表接口”部分。队列-用于在处理之前容纳多个元素的集合。除了基本的“收集”操作外,“队列”还提供其他插入、提取和检查操作。队列通常(但不一定)以FIFO(先进先出)方式对元素进行排序。例外情况包括优先级队列,它根据提供的比较器或元素的自然排序对元素进行排序。无论使用何种排序,队列的头都是将通过调用remove或poll来移除的元素。在FIFO队列中,所有新元素都插入到队列的尾部。其他类型的队列可以使用不同的放置规则。每个队列实现都必须指定其排序属性。另请参阅队列接口部分。
- Deque-在处理之前用于容纳多个元素的集合。除了基本的“收集”操作外,Deque还提供额外的插入、提取和检查操作。
- Deques既可以用作FIFO(先进先出)也可以用作LIFO(后进先出)。在deque中,所有新元素都可以在两端插入、检索和移除。另请参阅Deque接口部分。
- 映射-将关键点映射到值的对象。映射不能包含重复的键;每个键最多可以映射到一个值。如果您使用过Hashtable,那么您已经熟悉了Map的基本知识。
最后两个核心集合接口只是Set和Map的排序版本:
- SortedSet-一个按升序维护其元素的集合。提供了几个额外的操作来利用订购。排序集用于自然排序集,例如单词列表和成员名册。另请参阅“排序集接口”部分。
- SortedMap-一个按升序维护映射的Map。这是SortedSet的Map模拟。排序映射用于键/值对的自然排序集合,例如字典和电话簿。另请参阅“已排序映射接口”部分。
三、集合接口
集合表示一组称为其元素的对象。Collection接口用于传递需要最大通用性的对象集合。例如,按照惯例,所有通用集合实现都有一个接受collection参数的构造函数。此构造函数被称为转换构造函数,它初始化新集合以包含指定集合中的所有元素,无论给定集合的子接口或实现类型如何。换句话说,它允许您转换集合的类型。
例如,假设您有一个Collection<String>c,它可能是List、Set或其他类型的Collection。这个习惯用法创建了一个新的ArrayList(List接口的实现),最初包含c中的所有元素。
List<String> list = new ArrayList<String>(c);
Collection接口包含执行基本操作的方法,如int size()、boolean isEmpty()、boolean contains(Object element)、boolean add(E element)、boolean remove(Object element)和Iterator<E>Iterator()。
它还包含对整个集合进行操作的方法,如boolean containsAll(Collection<?>c)、boolean addAll(Collection<±extends E>c)、boolean removeAll(Collection>?>c(Collection<?>c))、boole retanall(Collection<>c)和void clear()。
数组操作的其他方法(如Object[] toArray()
and <T> T[] toArray(T[] a)
)也存在。
在JDK8及更高版本中,Collection接口还公开了Stream<E>Stream()和 Stream<E> parallelStream()
方法,用于从底层集合中获取顺序或并行流。(有关使用流的更多信息,请参阅题为“聚合操作”的课程。)
Collection接口实现了您所期望的功能,因为Collection表示一组对象。它有告诉您集合中有多少元素的方法(size,isEmpty),检查给定对象是否在集合中的方法(contains),向集合中添加和移除元素的方法,以及在集合上提供迭代器的方法(迭代器)。
add方法定义得足够一般,因此它对允许重复的集合和不允许重复的集都有意义。它保证在调用完成后Collection将包含指定的元素,如果Collection因调用而更改,则返回true。类似地,remove方法被设计为从Collection中删除指定元素的单个实例,假设它包含要开始的元素,并且如果Collection因此被修改,则返回true。
1、 遍历集合
遍历集合有三种方法:(1)使用聚合运算(2)使用for每个构造;(3)使用Iterator。
1.1 聚合操作
在JDK8及更高版本中,迭代集合的首选方法是获得流并对其执行聚合操作。聚合操作通常与lambda表达式一起使用,以使编程更具表达性,使用更少的代码行。以下代码依次遍历一组形状并打印出红色对象:
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()));
使用此API有许多不同的数据收集方法。例如,您可能希望将Collection的元素转换为String对象,然后将它们连接起来,用逗号分隔:
String joined = elements.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
或者也许将所有员工的工资相加:
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
1.2 for-each
for each构造允许您使用for循环简明地遍历集合或数组。以下代码使用for each构造在单独的一行上打印出集合的每个元素:
for (Object o : collection)
System.out.println(o);
1.2 Iterators
迭代器是一个对象,使您能够遍历集合,并根据需要选择性地从集合中删除元素。通过调用集合的迭代器方法,可以获得该集合的一个迭代器。以下是Iterator接口:
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); //optional
}
如果迭代有更多的元素,那么hasNext方法返回true,next方法返回迭代中的下一个元素。remove方法从基础集合中移除next返回的最后一个元素。remove方法每次调用next只能调用一次,如果违反此规则,就会抛出异常。
请注意,Iterator.remove是在迭代过程中修改集合的唯一安全方法;如果在迭代过程中以任何其他方式修改了基础集合,则行为是未指定的。
当需要执行以下操作时,请使用迭代器而不是用于每个构造:
- 删除当前元素。for each构造隐藏迭代器,因此不能调用remove。因此,for each构造不可用于过滤。
- 并行遍历多个集合。
下面的方法向您展示了如何使用迭代器来筛选任意集合——也就是说,遍历集合以删除特定元素:
static void filter(Collection<?> c) {
for (Iterator<?> it = c.iterator(); it.hasNext(); )
if (!cond(it.next()))
it.remove();
}
这段简单的代码是多态的,这意味着无论实现如何,它都适用于任何Collection。这个例子展示了使用Java集合框架编写多态算法是多么容易。
2、集合接口批量操作
批量操作对整个集合执行操作。您可以使用基本操作来实现这些简写操作,尽管在大多数情况下,这样的实现效率较低。以下是批量操作:
- containsAll-如果目标集合包含指定集合中的所有元素,则返回true。
- addAll-将指定集合中的所有元素添加到目标集合中。
- removeAll-从目标集合中删除指定集合中包含的所有元素。
- retainAll-从目标集合中删除指定集合中未包含的所有元素。也就是说,它只保留目标集合中也包含在指定集合中的那些元素。
- clear-从集合中删除所有元素。
如果在执行操作的过程中修改了目标Collection,那么addAll、removeAll和retainAll方法都返回true。
作为批量操作能力的一个简单示例,请考虑以下习语,从Collection,c中删除指定元素e的所有实例。
c.removeAll(Collections.singleton(e));
更具体地说,假设您想从集合中删除所有null元素。
c.removeAll(Collections.singleton(null));
这个习惯用法使用Collections.singleton,这是一个静态工厂方法,返回一个只包含指定元素的不可变Set。
3、集合接口数组操作
toArray方法是作为集合和旧API之间的桥梁提供的,旧API期望在输入时使用数组。数组操作允许将集合的内容转换为数组。不带参数的简单表单会创建一个新的Object数组。更复杂的形式允许调用方提供数组或选择输出数组的运行时类型。
例如,假设c是一个集合。下面的代码段将c的内容转储到新分配的Object数组中,该数组的长度与c中的元素数相同。
Object[] a = c.toArray();
假设已知c只包含字符串(可能是因为c的类型为Collection<String>)。下面的代码段将c的内容转储到新分配的String数组中,该数组的长度与c中的元素数量相同。
String[] a = c.toArray(new String[0]);
Java 集合全教程—Set 接口_Doker 多克的博客-CSDN博客
📢文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群💪💪💪
📢首发CSDN博客,创作不易,如果觉得文章不错,可以点赞👍收藏📁评论📒
📢你的支持和鼓励是我创作的动力❗❗❗