在集合中存储元素
探索集合接口
您需要了解的第一个接口是 Collection
接口。它模拟一个简单的集合,可以存储元素并提供不同的方式来检索它们。
如果您想运行此部分中的示例,您需要了解如何创建集合。我们还没有介绍 ArrayList
类,我们将在后面进行介绍。
处理单个元素的方法
让我们从在集合中存储和删除元素开始。涉及的两个方法是 add()
和 remove()
。
add(element)
: 在集合中添加一个元素。此方法在操作失败时返回一个boolean
。您在介绍中看到,对于List
,它不应该失败,而对于Set
,它可能会失败,因为集合不允许重复。remove(element)
: 从集合中删除给定的元素。此方法也返回一个布尔值,因为操作可能会失败。例如,当请求删除的项目不存在于集合中时,删除可能会失败。
您可以运行以下示例。在这里,您使用 ArrayList
实现创建 Collection
接口的实例。使用的泛型告诉 Java 编译器您想在此集合中存储 String
对象。 ArrayList
不是您可以使用的 Collection
的唯一实现。稍后会详细介绍。
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
System.out.println("strings = " + strings);
strings.remove("one");
System.out.println("strings = " + strings);
运行前面的代码应该打印以下内容
strings = [one, two]
strings = [two]
您可以使用 contains()
方法检查集合中是否存在元素。请注意,您可以检查任何类型的元素的存在。例如,检查集合中是否存在 User
对象是有效的 String
。这可能看起来很奇怪,因为此检查没有机会返回 true
,但编译器允许这样做。如果您使用 IDE 测试此代码,您的 IDE 可能会警告您测试集合中是否存在 User
对象 String
对象。
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
if (strings.contains("one")) {
System.out.println("one is here");
}
if (!strings.contains("three")) {
System.out.println("three is not here");
}
User rebecca = new User("Rebecca");
if (!strings.contains(rebecca)) {
System.out.println("Rebecca is not here");
}
运行此代码会产生以下结果
one is here
three is not here
Rebecca is not here
处理其他集合的方法
您看到的这第一组方法允许您处理单个元素。
有四种这样的方法:containsAll()
、addAll()
、removeAll()
和 retainAll()
。它们定义了对象集上的四个基本操作。
containsAll()
: 定义包含addAll()
: 定义并集removeAll()
: 定义补集retainAll()
: 定义交集。
第一个非常简单:containsAll()
以另一个集合作为参数,如果另一个集合的所有元素都包含在此集合中,则返回 true
。作为参数传递的集合不必与此集合类型相同:询问类型为 Collection<String>
的 String
集合是否包含在类型为 Collection<User>
的 User
集合中是合法的。
以下是如何使用此方法的示例
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
strings.add("three");
Collection<String> first = new ArrayList<>();
first.add("one");
first.add("two");
Collection<String> second = new ArrayList<>();
second.add("one");
second.add("four");
System.out.println("Is first contained in strings? " + strings.containsAll(first));
System.out.println("Is second contained in strings? " + strings.containsAll(second));
运行此代码会产生以下结果
Is first contained in strings? true
Is second contained in strings? false
第二个是 addAll()
。它允许您将给定集合的所有元素添加到此集合中。与 add()
方法一样,这在某些情况下可能会对某些元素失败。如果此集合在此调用中被修改,则此方法返回 true
。这是一个重要的理解点:获得 true
值并不意味着另一个集合的所有元素都已添加;这意味着至少添加了一个元素。
您可以在以下示例中看到 addAll()
的实际应用
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
strings.add("three");
Collection<String> first = new ArrayList<>();
first.add("one");
first.add("four");
boolean hasChanged = strings.addAll(first);
System.out.println("Has strings changed? " + hasChanged);
System.out.println("strings = " + strings);
运行此代码会产生以下结果
Has strings changed? true
strings = [one, two, three, one, four]
您需要知道,如果更改 Collection
的实现,运行此代码将产生不同的结果。此结果适用于 ArrayList
,正如您将在下面看到的那样,它对于 HashSet
来说将不同。
第三个是 removeAll()
。它删除此集合中包含在另一个集合中的所有元素。就像 contains()
或 remove()
一样,另一个集合可以定义在任何类型上;它不必与此集合的类型兼容。
您可以在以下示例中看到 removeAll()
的实际应用。
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
strings.add("three");
Collection<String> toBeRemoved = new ArrayList<>();
toBeRemoved.add("one");
toBeRemoved.add("four");
boolean hasChanged = strings.removeAll(toBeRemoved);
System.out.println("Has strings changed? " + hasChanged);
System.out.println("strings = " + strings);
运行此代码会产生以下结果
Has strings changed? true
strings = [two, three]
最后一个是 retainAll()
。此操作仅保留此集合中包含在另一个集合中的元素;所有其他元素都将被删除。再次强调,与 contains()
或 remove()
一样,另一个集合可以在任何类型上定义。
您可以在以下示例中看到 retainAll()
的实际应用。
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
strings.add("three");
Collection<String> toBeRetained = new ArrayList<>();
toBeRetained.add("one");
toBeRetained.add("four");
boolean hasChanged = strings.retainAll(toBeRetained);
System.out.println("Has strings changed? " + hasChanged);
System.out.println("strings = " + strings);
运行此代码会产生以下结果
Has strings changed? true
strings = [one]
处理集合本身的方法
然后,最后一批方法处理集合本身。
您有两个方法可以检查集合的内容。
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
if (!strings.isEmpty()) {
System.out.println("Indeed strings is not empty!");
}
System.out.println("The number of elements in strings is " + strings.size());
运行此代码会产生以下结果
Indeed strings is not empty!
The number of elements in strings is 2
然后,您可以通过简单地调用 clear()
来删除集合的内容。
Collection<String> strings = new ArrayList<>();
strings.add("one");
strings.add("two");
System.out.println("The number of elements in strings is " + strings.size());
strings.clear();
System.out.println("After clearing it, this number is now " + strings.size());
运行此代码会产生以下结果
The number of elements in strings is 2
After clearing it, this number is now 0
获取集合元素的数组
即使在您的应用程序中将元素存储在集合中可能比将它们放在数组中更有意义,但仍然有一些情况下您需要将它们获取到数组中。
Collection
接口为您提供了三种模式,以三种 toArray()
方法的重载形式,将集合的元素获取到数组中。
第一个是简单的 toArray()
调用,没有参数。这将以普通对象的数组形式返回您的元素。
这可能不是您需要的。如果您有一个 Collection<String>
,您可能更希望得到一个 String
数组。您仍然可以将 Object[]
转换为 String[]
,但不能保证这种转换在运行时不会失败。如果您需要类型安全,那么您可以调用以下两种方法之一。
toArray(T[] tab)
返回一个T
数组:T[]
toArray(IntFunction<T[]> generator)
,返回相同的类型,但语法不同。
最后两种模式有什么区别?第一个是可读性。创建 IntFunction<T[]>
的实例乍一看可能很奇怪,但用方法引用编写它实际上非常简单。
这是第一种模式。在这种模式中,您需要传递一个相应类型的数组。
Collection<String> strings = ...; // suppose you have 15 elements in that collection
String[] tabString1 = strings.toArray(new String[] {}); // you can pass an empty array
String[] tabString2 = strings.toArray(new String[15]); // or an array of the right size
作为参数传递的这个数组有什么用?如果它足够大,可以容纳集合中的所有元素,那么这些元素将被复制到数组中,并返回该数组。如果数组中还有空间,那么第一个未使用的单元格将被设置为 null。如果您传递的数组太小,那么将创建一个大小完全正确的数组来容纳集合中的元素。
这是这种模式的实际应用。
Collection<String> strings = List.of("one", "two");
String[] largerTab = {"three", "three", "three", "I", "was", "there"};
System.out.println("largerTab = " + Arrays.toString(largerTab));
String[] result = strings.toArray(largerTab);
System.out.println("result = " + Arrays.toString(result));
System.out.println("Same arrays? " + (result == largerTab));
运行前面的代码将得到以下结果。
largerTab = [three, three, three, I, was, there]
result = [one, two, null, I, was, there]
Same arrays? true
您可以看到,该数组已复制到参数数组的前几个单元格中,并且在它之后添加了 null
,因此该数组的最后一个元素保持不变。返回的数组与您作为参数提供的数组相同,但内容不同。
这是一个第二个示例,使用一个零长度数组。
Collection<String> strings = List.of("one", "two");
String[] zeroLengthTab = {};
String[] result = strings.toArray(zeroLengthTab);
System.out.println("zeroLengthTab = " + Arrays.toString(zeroLengthTab));
System.out.println("result = " + Arrays.toString(result));
运行此代码将得到以下结果。
zeroLengthTab = []
result = [one, two]
在这种情况下,将创建一个新的数组。
第二种模式使用构造函数方法引用来实现 IntFunction<T[]>
Collection<String> strings = ...;
String[] tabString3 = strings.toArray(String[]::new);
在这种情况下,将使用此函数创建一个正确类型的零长度数组,然后此方法将使用此数组作为参数调用 toArray()
。
此代码模式是在 JDK 8 中添加的,以提高 toArray()
调用的可读性。
使用谓词过滤集合元素
Java SE 8 为 Collection
接口添加了一项新功能:可以使用谓词过滤集合元素。
假设您有一个 List<String>
,您需要删除所有 null 字符串、空字符串和长度超过 5 个字符的字符串。在 Java SE 7 及更早版本中,您可以使用 Iterator.remove()
方法来执行此操作,在 if
语句中调用它。您将看到这种模式以及 Iterator
接口。使用 removeIf()
,您的代码将变得更加简单。
Predicate<String> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNullOrEmpty = isNull.or(isEmpty);
Collection<String> strings = new ArrayList<>();
strings.add(null);
strings.add("");
strings.add("one");
strings.add("two");
strings.add("");
strings.add("three");
strings.add(null);
System.out.println("strings = " + strings);
strings.removeIf(isNullOrEmpty);
System.out.println("filtered strings = " + strings);
运行此代码会产生以下结果
strings = [null, , one, two, , three, null]
filtered strings = [one, two, three]
再次强调,使用此方法将极大地提高应用程序代码的可读性和表达能力。
为 Collection 接口选择实现
在所有这些示例中,我们使用 ArrayList
来实现 Collection
接口。
事实是:Collections 框架没有直接实现 Collection
接口。 ArrayList
实现 List
,并且由于 List
扩展 Collection
,因此它也实现 Collection
。
如果您决定使用 Collection
接口来建模应用程序中的集合,那么在大多数情况下,选择 ArrayList
作为默认实现是最佳选择。您将在本教程的后面部分看到更多关于选择正确实现的讨论。
上次更新: 2021 年 9 月 14 日