本教程是针对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
)。
add
Collection
Collection
true
remove
Collection
true
Collection
有三种遍历集合的方法:(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]);