Java集合类的便利方法与框架探索
1. 子列表查找方法
在处理集合时,
indexOfSubList
和
lastIndexOfSubList
这两个方法十分实用。它们在操作时并不要求列表是有序的,其签名允许源列表和目标列表包含任何类型的元素。这里要注意,两个通配符可能代表两种不同的类型。这两个方法签名背后的设计决策,与
Collection
类中的
containsAll
、
retainAll
和
removeAll
方法是一致的。
2. 集合工厂方法
2.1 创建空集合
Collections
类提供了创建包含零个或多个对同一对象引用的集合的便捷方式。最简单的情况就是创建空集合:
<T> List<T> emptyList() // 返回空列表(不可变)
<K,V> Map<K,V> emptyMap() // 返回空映射(不可变)
<T> Set<T> emptySet() // 返回空集合(不可变)
空集合在实现返回集合值的方法时很有用,可用于表示没有值可返回的情况。每个方法都会返回
Collections
类的单例内部类的实例引用。由于这些实例是不可变的,所以可以安全地共享,调用这些工厂方法不会创建新对象。在 Java 泛型出现之前,
Collections
类的
EMPTY_SET
、
EMPTY_LIST
和
EMPTY_MAP
字段常用于相同目的,但现在它们的原始类型在使用时会产生未检查警告,因此不太实用。
2.2 创建单元素集合
Collections
类还提供了创建只包含单个成员的集合对象的方法:
<T> Set<T> singleton(T o)
// 返回只包含指定对象的不可变集合
<T> List<T> singletonList(T o)
// 返回只包含指定对象的不可变列表
<K,V> Map<K,V> singletonMap(K key, V value)
// 返回只将键 K 映射到值 V 的不可变映射
这些方法在为接受集合值的方法提供单个输入值时很有用。
2.3 创建包含多个相同对象副本的列表
可以使用
nCopies
方法创建包含给定对象多个副本的列表:
<T> List<T> nCopies(int n, T o)
// 返回包含 n 个对对象 o 引用的不可变列表
由于
nCopies
方法生成的列表是不可变的,它只需要包含一个物理元素就能提供所需长度的列表视图。这种列表常作为构建其他集合的基础,例如作为构造函数或
addAll
方法的参数。
下面是一个简单的示例,展示了如何使用这些集合工厂方法:
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CollectionFactoryExample {
public static void main(String[] args) {
// 创建空集合
List<String> emptyList = Collections.emptyList();
Map<String, Integer> emptyMap = Collections.emptyMap();
Set<String> emptySet = Collections.emptySet();
// 创建单元素集合
Set<String> singletonSet = Collections.singleton("single");
List<String> singletonList = Collections.singletonList("single");
Map<String, Integer> singletonMap = Collections.singletonMap("key", 1);
// 创建包含多个相同对象副本的列表
List<String> nCopiesList = Collections.nCopies(3, "copy");
System.out.println("Empty List: " + emptyList);
System.out.println("Empty Map: " + emptyMap);
System.out.println("Empty Set: " + emptySet);
System.out.println("Singleton Set: " + singletonSet);
System.out.println("Singleton List: " + singletonList);
System.out.println("Singleton Map: " + singletonMap);
System.out.println("nCopies List: " + nCopiesList);
}
}
在这个示例中,我们分别创建了空集合、单元素集合和包含多个相同对象副本的列表,并打印输出了这些集合的内容。
3. 集合包装器
Collections
类提供了包装器对象,通过三种方式修改标准集合类的行为:同步集合、使集合不可修改或检查添加到集合中的元素类型。这些包装器对象实现了与被包装对象相同的接口,并将工作委托给它们。其目的是限制工作执行的条件,这是保护代理模式的应用,代理控制对真实对象的访问。
3.1 同步集合
大多数框架类在设计上不是线程安全的,以避免不必要的同步开销(如遗留类
Vector
和
Hashtable
)。但在某些情况下,需要多个线程访问同一个集合,
Collections
类为此提供了同步包装器。
<T> Collection<T> synchronizedCollection(Collection<T> c);
<T> Set<T> synchronizedSet(Set<T> s);
<T> List<T> synchronizedList(List<T> list);
<K, V> Map<K, V> synchronizedMap(Map<K, V> m);
<T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);
<K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> m);
提供这些同步视图的类是有条件的线程安全的。虽然它们的每个操作都保证是原子的,但可能需要同步多个方法调用以获得一致的行为。特别是,迭代器必须在对集合进行同步的代码块内创建和使用,否则可能会抛出
ConcurrentModificationException
异常。这种同步是粗粒度的,如果应用程序大量使用同步集合,其有效并发性能将大大降低。
3.2 不可修改集合
不可修改的集合在任何试图更改其结构或组成元素的操作时,都会抛出
UnsupportedOperationException
异常。当需要允许客户端读取内部数据结构,但又不希望其被修改时,这很有用。将数据结构放在不可修改的包装器中可以防止客户端更改它,但如果集合中的对象是可修改的,则无法阻止客户端更改这些对象。在某些情况下,可能需要通过提供防御性副本来保护内部数据结构,或者将这些对象也放在不可修改的包装器中。
<T> Collection<T> unmodifiableCollection(Collection<? extends T> c)
<T> Set<T> unmodifiableSet(Set<? extends T> s)
<T> List<T> unmodifiableList(List<? extends T> list)
<K, V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)
<T> SortedSet<T> unmodifiableSortedSet(SortedSet<? extends T> s)
<K, V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m)
3.3 检查型集合
编译器的未检查警告提醒我们要特别注意避免运行时类型违规。例如,将类型化集合引用传递给非泛型库方法后,无法确定该方法是否只向集合中添加了正确类型的元素。此时,可以传递一个检查型包装器,它会检查添加到集合中的每个元素是否属于创建包装器时指定的类型。
static <E> Collection
checkedCollection(Collection<E> c, Class<E> elementType)
static <E> List
checkedList(List<E> c, Class<E> elementType)
static <E> Set
checkedSet(Set<E> c, Class<E> elementType)
static <E> SortedSet
checkedSortedSet(SortedSet<E> c, Class<E> elementType)
static <K, V> Map
checkedMap(Map<K, V> c, Class<K> keyType, Class<V> valueType)
static <K, V> SortedMap
checkedSortedMap(SortedMap<K, V> c, Class<K> keyType,Class<V> valueType)
下面是一个关于集合包装器使用的示例:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionWrapperExample {
public static void main(String[] args) {
// 创建一个普通列表
List<String> list = new ArrayList<>();
list.add("element1");
list.add("element2");
// 创建同步列表
List<String> synchronizedList = Collections.synchronizedList(list);
// 创建不可修改列表
List<String> unmodifiableList = Collections.unmodifiableList(list);
// 创建检查型列表
List<String> checkedList = Collections.checkedList(list, String.class);
try {
// 尝试修改不可修改列表
unmodifiableList.add("newElement");
} catch (UnsupportedOperationException e) {
System.out.println("尝试修改不可修改列表时抛出异常: " + e.getMessage());
}
try {
// 尝试向检查型列表添加错误类型的元素
// 这里假设我们尝试添加一个 Integer 类型的元素
// 由于编译时类型检查,下面这行代码无法通过编译
// checkedList.add(123);
} catch (ClassCastException e) {
System.out.println("尝试向检查型列表添加错误类型元素时抛出异常: " + e.getMessage());
}
}
}
在这个示例中,我们创建了一个普通列表,并分别使用同步包装器、不可修改包装器和检查型包装器对其进行包装。然后尝试修改不可修改列表和向检查型列表添加错误类型的元素,观察相应的异常抛出情况。
集合包装器使用流程
graph TD
A[创建普通集合] --> B[选择包装器类型]
B --> C{同步包装器}
B --> D{不可修改包装器}
B --> E{检查型包装器}
C --> F[使用 synchronizedXXX 方法包装]
D --> G[使用 unmodifiableXXX 方法包装]
E --> H[使用 checkedXXX 方法包装]
F --> I[在同步代码块中使用集合]
G --> J[尝试修改会抛出异常]
H --> K[添加错误类型元素会检查失败]
4. 其他实用方法
Collections
类还提供了许多实用方法,下面按字母顺序进行介绍:
4.1 addAll
<T> boolean addAll(Collection<? super T> c, T... elements)
// 将所有指定元素添加到指定集合中
该方法是一种方便且高效的方式,可用于用单个元素或数组内容初始化集合。例如:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AddAllExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "element1", "element2", "element3");
System.out.println("使用 addAll 方法后的列表: " + list);
}
}
4.2 asLifoQueue
<T> Queue<T> asLifoQueue(Deque<T> deque)
// 将双端队列视为后进先出(LIFO)队列
队列可以对其元素施加各种不同的排序,但没有标准的
Queue
实现提供 LIFO 排序。而双端队列实现如果从元素添加的同一端移除元素,则都支持 LIFO 排序。
asLifoQueue
方法允许通过简洁的
Queue
接口使用此功能。示例如下:
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Queue;
public class AsLifoQueueExample {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();
deque.add("element1");
deque.add("element2");
deque.add("element3");
Queue<String> lifoQueue = java.util.Collections.asLifoQueue(deque);
System.out.println("LIFO 队列的第一个元素: " + lifoQueue.poll());
}
}
4.3 disjoint
boolean disjoint(Collection<?> c1, Collection<?> c2)
// 如果 c1 和 c2 没有共同元素,则返回 true
使用此方法时需要注意,实现可能会遍历任一集合,检查一个集合的元素是否包含在另一个集合中。因此,如果两个集合确定包含关系的方式不同,该方法的结果是不确定的。例如,一个集合是
SortedSet
,其包含关系由自然排序或比较器决定,而另一个集合是
Set
,其包含关系由元素的
equals
方法决定,就可能出现这种情况。示例如下:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class DisjointExample {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("element1");
list1.add("element2");
List<String> list2 = new ArrayList<>();
list2.add("element3");
list2.add("element4");
boolean isDisjoint = Collections.disjoint(list1, list2);
System.out.println("两个集合是否没有共同元素: " + isDisjoint);
}
}
4.4 enumeration
<T> Enumeration<T> enumeration(Collection<T> c)
// 返回指定集合的枚举器
该方法用于与接受
Enumeration
类型参数的 API 进行互操作,
Enumeration
是
Iterator
的旧版本。它返回的枚举器产生的元素与集合的
Iterator
相同,顺序也相同。此方法与
list
方法成对出现,
list
方法将
Enumeration
值转换为
ArrayList
。示例如下:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
public class EnumerationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("element1");
list.add("element2");
list.add("element3");
Enumeration<String> enumeration = Collections.enumeration(list);
while (enumeration.hasMoreElements()) {
System.out.println("枚举器元素: " + enumeration.nextElement());
}
}
}
4.5 frequency
int frequency(Collection<?> c, Object o)
// 返回集合 c 中等于 o 的元素数量
如果提供的值
o
为
null
,则
frequency
方法返回集合
c
中
null
元素的数量。示例如下:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class FrequencyExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("element1");
list.add("element2");
list.add("element1");
int freq = Collections.frequency(list, "element1");
System.out.println("element1 在集合中的出现次数: " + freq);
}
}
4.6 list
<T> ArrayList<T> list(Enumeration<T> e)
// 返回包含指定枚举器返回元素的 ArrayList
该方法用于与返回
Enumeration
类型结果的 API 进行互操作,返回的
ArrayList
包含的元素与枚举器提供的相同,顺序也相同。示例如下:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Vector;
public class ListExample {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.add("element1");
vector.add("element2");
vector.add("element3");
Enumeration<String> enumeration = vector.elements();
ArrayList<String> list = Collections.list(enumeration);
System.out.println("从枚举器转换得到的列表: " + list);
}
}
4.7 newSetFromMap
<E> Set<E> newSetFromMap(Map<E, Boolean> map)
// 返回由指定映射支持的集合
许多集合(如
TreeSet
和
NavigableSkipListSet
)是由映射实现的,并共享其排序、并发和性能特性。但有些映射(如
WeakHashMap
和
IdentityHashMap
)没有标准的
Set
等价物。
newSetFromMap
方法的目的是为这些映射提供等价的
Set
实现。该方法会包装其参数,提供的映射必须为空,且后续不应直接访问。以下是使用它创建弱引用
HashSet
的标准示例:
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
public class NewSetFromMapExample {
public static void main(String[] args) {
Set<Object> weakHashSet = Collections.newSetFromMap(
new WeakHashMap<Object, Boolean>());
System.out.println("创建的弱引用 HashSet: " + weakHashSet);
}
}
4.8 reverseOrder
<T> Comparator<T> reverseOrder()
// 返回反转自然排序的比较器
该方法提供了一种简单的方式,用于按自然顺序的逆序对
Comparable
对象集合进行排序或维护。例如:
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
public class ReverseOrderExample {
public static void main(String[] args) {
SortedSet<Integer> s = new TreeSet<Integer>(Collections.reverseOrder());
Collections.addAll(s, 1, 2, 3);
System.out.println("按逆序排序的集合: " + s);
}
}
还有另一种形式的
reverseOrder
方法:
<T> Comparator<T> reverseOrder(Comparator<T> cmp)
该方法与前面的方法类似,但它反转的是作为参数提供的比较器的顺序。当传入
null
时,其行为对于
Collections
类的方法来说是不寻常的。
Collections
类的方法规定,如果提供的集合或类对象为
null
,则会抛出
NullPointerException
,但此方法在传入
null
时,返回的结果与调用
reverseOrder()
相同,即返回一个反转对象集合自然顺序的比较器。
其他实用方法使用总结
| 方法名 | 功能描述 | 示例代码 |
|---|---|---|
addAll
| 将所有指定元素添加到指定集合中 |
Collections.addAll(list, "element1", "element2");
|
asLifoQueue
| 将双端队列视为后进先出队列 |
Queue<String> lifoQueue = Collections.asLifoQueue(deque);
|
disjoint
| 判断两个集合是否没有共同元素 |
boolean isDisjoint = Collections.disjoint(list1, list2);
|
enumeration
| 返回指定集合的枚举器 |
Enumeration<String> enumeration = Collections.enumeration(list);
|
frequency
| 返回集合中等于指定对象的元素数量 |
int freq = Collections.frequency(list, "element1");
|
list
|
将枚举器转换为
ArrayList
|
ArrayList<String> list = Collections.list(enumeration);
|
newSetFromMap
| 返回由指定映射支持的集合 |
Set<Object> weakHashSet = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
|
reverseOrder
| 返回反转自然排序的比较器 |
SortedSet<Integer> s = new TreeSet<Integer>(Collections.reverseOrder());
|
这些方法为 Java 集合的操作提供了丰富的功能,开发者可以根据具体需求灵活使用。通过合理运用这些方法,可以提高代码的效率和可读性。例如,在需要对集合进行元素添加、排序、查找等操作时,选择合适的方法可以避免手动编写复杂的逻辑。同时,对于线程安全、类型检查等方面的需求,也可以借助相关的包装器和方法来实现。希望本文能帮助开发者更好地理解和使用 Java 集合类的各种功能。
5. 集合操作与实用方法的综合应用
在实际开发中,我们常常需要综合运用各种集合操作和实用方法来解决复杂的问题。下面通过一个示例,展示如何将前面介绍的方法组合使用,实现一个简单的数据处理任务。
5.1 需求描述
假设有一个包含学生信息的列表,每个学生信息用一个
Map
表示,包含学生的姓名、年龄和成绩。我们需要完成以下任务:
1. 筛选出成绩大于 80 分的学生。
2. 对筛选后的学生按年龄进行降序排序。
3. 输出排序后的学生姓名列表。
5.2 代码实现
import java.util.*;
public class StudentDataProcessing {
public static void main(String[] args) {
// 初始化学生信息列表
List<Map<String, Object>> students = new ArrayList<>();
Map<String, Object> student1 = new HashMap<>();
student1.put("name", "Alice");
student1.put("age", 20);
student1.put("score", 85);
students.add(student1);
Map<String, Object> student2 = new HashMap<>();
student2.put("name", "Bob");
student2.put("age", 22);
student2.put("score", 78);
students.add(student2);
Map<String, Object> student3 = new HashMap<>();
student3.put("name", "Charlie");
student3.put("age", 21);
student3.put("score", 90);
students.add(student3);
// 筛选出成绩大于 80 分的学生
List<Map<String, Object>> highScoreStudents = new ArrayList<>();
for (Map<String, Object> student : students) {
int score = (int) student.get("score");
if (score > 80) {
highScoreStudents.add(student);
}
}
// 对筛选后的学生按年龄进行降序排序
Collections.sort(highScoreStudents, new Comparator<Map<String, Object>>() {
@Override
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
int age1 = (int) o1.get("age");
int age2 = (int) o2.get("age");
return Integer.compare(age2, age1);
}
});
// 输出排序后的学生姓名列表
List<String> names = new ArrayList<>();
for (Map<String, Object> student : highScoreStudents) {
names.add((String) student.get("name"));
}
System.out.println("成绩大于 80 分且按年龄降序排序的学生姓名列表: " + names);
}
}
5.3 代码解释
-
初始化学生信息列表
:创建一个
List,其中每个元素是一个Map,包含学生的姓名、年龄和成绩。 -
筛选学生
:遍历学生列表,使用
if语句筛选出成绩大于 80 分的学生,并将其添加到新的列表中。 -
排序学生
:使用
Collections.sort方法对筛选后的学生列表进行排序,通过自定义Comparator实现按年龄降序排序。 - 输出姓名列表 :遍历排序后的学生列表,提取学生姓名并添加到新的列表中,最后输出该列表。
5.4 操作步骤总结
graph TD
A[初始化学生信息列表] --> B[筛选成绩大于 80 分的学生]
B --> C[对筛选后的学生按年龄降序排序]
C --> D[提取排序后学生的姓名]
D --> E[输出学生姓名列表]
6. 集合使用的注意事项与性能优化
在使用 Java 集合时,有一些注意事项和性能优化的技巧需要我们了解,以确保代码的高效性和稳定性。
6.1 线程安全问题
大多数 Java 集合类不是线程安全的,如
ArrayList
、
HashMap
等。在多线程环境下使用这些集合时,可能会出现数据不一致或并发修改异常。为了保证线程安全,可以使用同步包装器或并发集合类。
-
同步包装器
:使用
Collections
类的同步包装器方法,如
synchronizedList
、
synchronizedMap
等,将普通集合转换为线程安全的集合。但要注意,这种同步是粗粒度的,可能会影响性能。
-
并发集合类
:Java 提供了一些并发集合类,如
ConcurrentHashMap
、
ConcurrentLinkedQueue
等,这些类在设计上考虑了多线程环境,具有更好的并发性能。
6.2 性能优化
不同的集合类在不同的操作场景下有不同的性能表现,选择合适的集合类可以提高代码的性能。
-
插入和删除操作
:如果需要频繁进行插入和删除操作,
LinkedList
可能比
ArrayList
更合适,因为
LinkedList
在插入和删除元素时的时间复杂度为 O(1),而
ArrayList
可能需要移动大量元素,时间复杂度为 O(n)。
-
查找操作
:如果需要频繁进行查找操作,
HashMap
或
TreeMap
可能更合适。
HashMap
的查找时间复杂度为 O(1),而
TreeMap
可以保持元素的有序性,查找时间复杂度为 O(log n)。
6.3 避免使用原始类型
原始类型是指没有指定泛型类型的集合,如
List
、
Map
等。使用原始类型会导致编译器无法进行类型检查,可能会在运行时抛出
ClassCastException
异常。因此,建议始终使用泛型来指定集合的元素类型。
6.4 注意事项总结
| 注意事项 | 描述 | 解决方案 |
|---|---|---|
| 线程安全问题 | 大多数集合类不是线程安全的,多线程环境下可能出现数据不一致或并发修改异常 | 使用同步包装器或并发集合类 |
| 性能优化 | 不同集合类在不同操作场景下性能不同 | 根据具体操作场景选择合适的集合类 |
| 避免使用原始类型 | 原始类型无法进行类型检查,可能导致运行时异常 | 始终使用泛型指定集合元素类型 |
7. 总结
Java 集合类提供了丰富的功能和便利的方法,通过合理使用这些方法和集合类,可以提高代码的效率和可读性。本文介绍了集合工厂方法、集合包装器、其他实用方法以及集合操作的综合应用,并强调了集合使用的注意事项和性能优化技巧。
在实际开发中,我们应根据具体需求选择合适的集合类和方法,同时注意线程安全和性能问题。通过不断实践和学习,我们可以更好地掌握 Java 集合类的使用,编写出高效、稳定的代码。希望本文能对开发者在使用 Java 集合类时有所帮助。
通过本文的介绍,我们对 Java 集合类的便利方法和框架有了更深入的了解。从集合的创建、操作到性能优化,每个环节都有其独特的特点和注意事项。在实际应用中,我们要根据具体的业务需求,灵活运用这些知识,以实现高效、稳定的代码。同时,不断学习和实践,探索更多集合类的高级用法,将有助于我们提升编程能力和解决问题的能力。
Java集合类便利方法与框架探索
超级会员免费看
781

被折叠的 条评论
为什么被折叠?



