本教程是针对JDK 8编写的。本页中描述的示例和实践不利用后续版本中引入的改进,可能使用不再可用的技术。
请参阅Java语言更改了解Java SE 9及其后续版本中的更新语言特性的概述。
请参阅JDK发行说明了解所有JDK版本的新功能、增强功能以及已删除或弃用选项的信息。
Collection(集合)代表一组对象,这些对象称为其元素。 Collection 接口用于传递希望具有最大普遍性的对象集合。 例如,按照约定,所有通用的集合实现都有一个以 Collection 为参数的构造函数。 这个构造函数被称为转换构造函数,它将新的集合初始化为包含指定集合中的所有元素,无论给定集合的子接口或实现类型是什么。 换句话说,它允许您转换集合的类型。
例如,假设您有一个 Collection<String> c,它可以是 List、Set 或其他类型的 Collection。 这个习惯用法创建一个新的 ArrayList(List 接口的实现),最初包含 c 中的所有元素。
List<String> list = new ArrayList<String>(c);
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(),用于从底层集合获取顺序或并行流(有关使用流的更多信息,请参见题为 Aggregate Operations 的教程)。
Collection 接口的功能与其表示一组对象的特性相符。它具有告诉您集合中有多少元素的方法(size、isEmpty)、检查给定对象是否在集合中的方法(contains)、向集合中添加和删除元素的方法(add、remove)以及提供集合上的迭代器的方法(iterator)。
addCollectionCollectiontrueremoveCollectiontrueCollection
有三种遍历集合的方法:(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)));
这只是关于使用流和聚合操作可以做的一些例子。有关更多信息和示例,请参阅题为聚合操作的教程。
集合框架一直提供了一些所谓的“批量操作”作为其API的一部分。这些方法操作整个集合,如containsAll、addAll、removeAll等。不要将这些方法与JDK 8中引入的聚合操作混淆。新的聚合操作和现有的批量操作(containsAll、addAll等)之间的关键区别在于,旧版本的操作都是可变的,意味着它们都会修改底层集合。相比之下,新的聚合操作不会修改底层集合。当使用新的聚合操作和lambda表达式时,您必须小心避免变异,以免在以后从并行流运行代码时引入问题。
for-each结构允许您使用for循环简洁地遍历集合或数组 — 请参阅for语句。以下代码使用for-each结构在单独的行上打印出集合的每个元素。
for (Object o : collection)
System.out.println(o);
Iterator是一种对象,它使您能够遍历集合并选择性地从集合中删除元素(如果需要)。通过调用集合的iterator方法,可以获取集合的Iterator。以下是Iterator接口的定义。
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); //可选
}
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集合框架编写多态算法的简便性。
批量操作在整个Collection上执行操作。您可以使用基本操作来实现这些简便操作,但在大多数情况下,这样的实现效率较低。以下是批量操作:
containsAll — 如果目标Collection包含指定Collection中的所有元素,则返回true。addAll — 将指定Collection中的所有元素添加到目标Collection中。removeAll — 从目标Collection中删除指定Collection中也包含的所有元素。retainAll — 从目标Collection中删除所有不在指定Collection中的元素。即,只保留目标Collection中也包含在指定Collection中的元素。clear — 从Collection中删除所有元素。addAll、removeAll和retainAll方法在执行操作过程中,如果目标Collection被修改,则都返回true。
作为批量操作功能的一个简单示例,考虑以下用法,从Collection c中删除所有指定元素e的实例。
c.removeAll(Collections.singleton(e));
更具体地说,假设您想从Collection中删除所有null元素。
c.removeAll(Collections.singleton(null));
此用法使用了Collections.singleton,它是一个静态工厂方法,返回一个只包含指定元素的不可变Set。
toArray方法用作集合和旧版API之间的桥梁,旧版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]);