Collection(集合)表示一组称其为元素的对象。Collection
接口用于传递需要最大通用性的对象的集合。例如,按照惯例,所有通用集合实现都有一个采用Collection
参数的构造器。此构造器称为转换构造器(conversion constructor),它初始化新集合以包含指定集合中的所有元素,无论给定集合的子接口或实现类型如何。换句话说,它允许您转换集合的类型。
例如,假设您有一个Collection<String> c
,它可以是一个List
,一个Set
或另一种Collection
。这个习惯用法创建了一个新的ArrayList
(List
接口的实现),最初包含c
中的所有元素。
List<String> list = new ArrayList<String>(c);
或者——如果您使用的是JDK 7或更高版本,则可以使用菱形运算符:
List<String> list = new ArrayList<>(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)
,boolean retainAll(Collection<?> c)
,和 void clear()
。
数组操作的其他方法(例如Object[] toArray()
和<T> T[] toArray(T[] a)
。
在JDK 8和更高版本中,Collection
接口还公开了方法Stream<E> stream()
和Stream<E> parallelStream()
,用于从基础集合中获取顺序或并行流。(有关使用流的更多信息,请参阅名为“聚合操作”的课程 。)<br />
在Collection
代表一组对象的情况下,Collection
接口可以实现您的期望。它具有告诉您集合中有多少个元素的方法(size
,isEmpty
),检查集合中是否有给定对象的方法(contains
),向集合中添加和删除元素的方法(add
,remove
), 以及在集合上提供迭代器(iterator
)的方法。
通常对add
方法进行足够的定义,以使它对于允许重复和不允许重复的集合有意义。它保证在调用完成后Collection
将包含指定的元素,如果Collection
因调用而发生更改,则返回true
。同样,remove
方法被设计为从Collection
中移除指定元素的单个实例,前提是该元素包含以该元素开头的元素,如果结果是Collection
被修改,则返回true
。
遍历集合
有三种遍历集合的方式:(1)使用聚合操作(2)for-each
构造,以及(3)使用Iterator
。
聚合操作
在JDK 8和更高版本中,对集合进行迭代的首选方法是获取流并对其执行聚合操作。聚合操作通常与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)));
这些只是您可以使用流和聚合操作执行的几个示例。有关更多信息和示例,请参阅“ 聚合操作 ”课程 。
Collections框架一直提供许多所谓的“批量操作”作为其API的一部分。这些方法包括对整个集合进行操作的方法,例如containsAll
,addAll
,removeAll
等。请勿将这些方法与JDK 8中引入的聚合操作混淆。新的聚合操作与现有的批量操作(containsAll
,addAll
等)之间的主要区别是旧版本都是可变的,这意味着它们都修改了基础集合。相反,新的聚合操作不会修改基础集合。使用新的聚合操作和lambda表达式时,必须注意避免突变,以免将来出现问题,如果以后从并行流中运行代码。
for-each构造
for-each
构造使您可以使用for
循环简洁地遍历集合或数组-请参见 for语句。以下代码使用for-each
构造,在单独的一行上打印出集合的每个元素。
for (Object o : collection)
System.out.println(o);
迭代器
迭代器是使您能够遍历集合并根据需要有选择地从集合中删除元素的对象。您可以通过调用其iterator
方法来获得集合的Iterator
。以下是Iterator接口。
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); //optional
}
如果迭代具有更多元素,则hasNext
方法将返回true
,而next
方法将返回迭代中的下一个元素。remove
方法从基础Collection
中移除next
返回的最后一个元素。每次调用next
时,remove
方法只能被调用一次,如果违反了该规则,则抛出异常。
注意,Iterator.remove
是在迭代过程中修改集合的唯一安全方法。如果在进行迭代时以任何其他方式修改基础集合,则行为未指定。
在需要时使用Iterator
代替for-each
构造:
- 删除当前元素。
for-each
构造隐藏了迭代器,因此您无法调用remove
。因此,for-each
构造不适用于过滤。 - 并行迭代多个集合。
以下方法向您展示了如何使用Iterator
过滤任意Collection
对象——遍历集合以除去特定元素。
static void filter(Collection<?> c) {
for (Iterator<?> it = c.iterator(); it.hasNext(); )
if (!cond(it.next()))
it.remove();
}
这一段简单的代码是多态的,这意味着它适用于任何 Collection
不论何种实现。此示例说明了使用Java Collections Framework编写多态算法有多么容易。
集合接口批量操作
批量操作(Bulk operations)对整个Collection
对象执行操作。您可以使用基本操作来实现这些速记操作,尽管在大多数情况下,此类实现的效率较低。以下是批量操作:
containsAll
——如果目标Collection
包含指定Collection
中的所有元素,则返回true
。addAll
——将指定Collection
对象中的所有元素添加到目标Collection
。removeAll
—从目标Collection
中删除其也包含在指定Collection
中的所有元素。retainAll
—从目标Collection
中删除所有未包含在指定Collection
中的元素。也就是说,它仅保留目标Collection
中也包含在指定Collection
中的那些元素。clear
—从Collection
中删除所有元素。
如果在执行操作的过程中修改了目标Collection
,则addAll
,removeAll
和retainAll
方法均返回true
。
作为批量操作功能的简单示例,请考虑以下惯用法以从Collection ``c
中删除指定元素e
的所有实例。
c.removeAll(Collections.singleton(e));
更具体地说,假设您要删除Collection
中的所有null
元素。
c.removeAll(Collections.singleton(null));
这个习惯用法使用Collections.singleton
,这是一个静态工厂方法,它返回仅包含指定元素的不可变Set
对象。
集合接口数组操作
提供toArray
方法作为集合与期望输入上有数组的旧API之间的桥梁。数组操作允许将Collection
的内容转换为数组。没有参数的简单形式创建一个新的Object
数组。更为复杂的形式允许调用方提供一个数组或选择输出数组的运行时类型。
例如,假设c
是一个Collection
。以下代码片段将c
的内容转储到新分配的Object
数组中,该对象的长度与c
中的元素数相同。
Object[] a = c.toArray();
假设已知c
仅包含字符串(可能是因为c
的类型为Collection<String>
)。 以下代码片段将c
的内容转储到新分配的String
数组中,该数组的长度与c
中的元素数相同。
String[] a = c.toArray(new String[0]);