Java Collection 接口

一个 Collection 表示一组对象,这组对象称为 Collection 的元素。Collection 接口可用于传递一组有共同特征的数据。例如,按照惯例,所有集合类的具体实现都要有一个带有 Collection 参数的构造方法,这个构造方法称为转换构造器,它将使用参数中的 Collection 来初始化当前集合。

Java 集合框架

例如,你有一个集合 Collection<String> c,它可能是 List,Set 或者其它类型的 Collection,现在创建一个新的 ArrayList(List 接口的一个实现),使用 c 中的元素来初始化:

List<String> list = new ArrayList<String>(c);

或者,如果使用了 Java 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(),用于从底层集合中获取串行和并行流。

Collection 接口提供了 add 和 remove 方法用于添加和删除集合中的元素,如果返回 true 表示操作成功。

遍历集合

遍历集合有三种方式:1)使用集合操作 2)使用 for 循环 3)使用迭代器。

使用集合操作

从 Java 8 开始,遍历集合推荐先获取流然后再使用集合操作的方式。使用集合操作通常会结合 Lambda 表达式一起使用,这样可以用更少的代码编写更丰富的程序。下面的代码使用串行流遍历集合中的 Shape 对象,并且打印 red 对象:

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

使用这种方式,有很丰富的方法来收集数据,例如,你可能想将 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 等。不要将这些方法和 Java 8 引入的集合操作混淆。集合操作和批量处理的最主要不同就是批量操作会更改集合中的内容,而集合操作不会更改集合中的内容。当使用集合操作和 Lambda 表达式的时候,你需要小心地避免更改集合中的数据,这样做不会在以后产生不必要的问题。

for 循环

可以使用 for 循环简单地遍历 Collection:

for (Object o : collection)
    System.out.println(o);

迭代器

Iterator 是一个迭代器对象,可以通过它遍历 Collect 或选择性地删除集合中的元素。可以通过 iterator 方法获取 Iterator 对象,下面是 Iterator 接口声明:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove(); //optional
}

如果迭代器接下来还有元素,则 hasNext 方法返回 true,并且 next 方法返回下一个元素。remove 方法从 Collection 中删除上一个 next 方法返回的元素。remove 方法只能在每次 next 方法之后调用一次,如果违反这个规则,会抛出异常。

注意 Iterator.remove 方法是在遍历的时候修改集合的唯一安全的方法。如果在遍历的时候使用其它的方式更改了集合中的内容,那么整个遍历行为将会不可控。

以下情况你可以使用迭代器替换 for 循环:

  • 需要删除当前元素,在 for 循环结构中不能删除当前元素。
  • 并行遍历多个集合的时候。

下面的代码演示了如何使用 Iterator 遍历 Collection,并且在满足条件时删除当前元素:

static void filter(Collection<?> c) {
    for (Iterator<?> it = c.iterator(); it.hasNext(); )
        if (!cond(it.next()))
            it.remove();
}

这个简单的例子可以使用多态,意思是可以传入任何 Collection 类的实现。这个例子演示了使用 Java 集合框架可以很简单的创建多态算法。

Collection 接口的批量操作

批量操作是在整个 Collection 上执行操作。你可以使用基本操作来实现这些批量操作,但是在某些情况下,这样的实现效率会较低。下面是批量操作方法:

  • containsAll – 如果目标 Collection 包含了指定 Collection 中的所有元素,那么返回 true。
  • addAll – 将指定 Collection 中的所有元素添加到目标 Collection。
  • removeAll – 从目标 Collection 中删除指定 Collection 中的所有元素。
  • retainAll – 删除所有目标 Collection 中不在指定 Collection 中的元素。也就是说,只会保留目标 Collection 和指定 Collection 中共同拥有的元素。
  • clear – 清除 Collection 中的所有元素。

如果按要求修改了 Collection,那么 addAll、 removeAll、 和 retainAll 会返回 true

下面用一个简单的例子来演示批量操作,从集合 c 中删除集合 e 中的所有元素:

c.removeAll(Collections.singleton(e));

从 Collection 中删除 null:

c.removeAll(Collections.singleton(null));

这里使用了 Collections.singleton 方法,它是一个静态方法,将返回一个包含了指定元素的不可变 Set 对象。

Collection 接口中的数组操作

toArray 方法提供了 Collection 和数组之间的桥梁。调用这个方法可以将 Collection 中的内容转换为数组。

例如,假设 c 是 Collection 类型,下面的代码将 c 中的内容转换到新创建的 Object 数组中,数组长度和 c 中元素个数一致:

Object[] a = c.toArray();

如果知道 c 中的元素类型是 String(可能是因为声明类型为 Collection<String>),下面的代码将 c 中的内容转换到新创建的 String 数组中,数组长度和 c 中元素的个数一致:

String[] a = c.toArray(new String[0]);