1. Java数组与集合基础概念解析
在Java编程中,数组和集合是最基础也是最常用的数据结构。数组是一种固定长度的线性数据结构,而集合框架(Collection Framework)则提供了更灵活的动态数据结构实现。理解它们的长度获取方式和遍历操作,是每个Java开发者必须掌握的核心技能。
数组在内存中是连续存储的,这使得它的随机访问效率极高(O(1)时间复杂度)。但数组的长度一旦初始化就无法改变,这是它与集合最显著的区别之一。Java中的数组可以存储基本类型(如int、char等)和对象类型,而集合只能存储对象类型(基本类型需要通过包装类转换)。
集合框架主要分为三大类:
- List:有序可重复集合,典型实现有ArrayList和LinkedList
- Set:无序不可重复集合,典型实现有HashSet和TreeSet
- Map:键值对映射,不是严格意义上的集合,但常与集合一起讨论,典型实现有HashMap和TreeMap
注意:虽然Map接口不属于Collection接口的子接口,但在日常开发中我们通常把它视为集合框架的一部分,因为它与集合操作密切相关。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数组的长度获取与遍历方法
2.1 数组长度获取
Java数组的长度是通过数组对象的length属性获取的,这是一个final字段,不是方法:
java复制int[] numbers = {1, 2, 3, 4, 5};
int length = numbers.length; // 正确方式
常见误区是误用size()方法(这是集合的方法)或length()方法(这是String类的方法):
java复制// 错误示范
int wrong1 = numbers.size(); // 编译错误
int wrong2 = numbers.length(); // 编译错误
对于多维数组,获取长度需要注意维度:
java复制int[][] matrix = {{1,2}, {3,4}, {5,6}};
int rows = matrix.length; // 3
int cols = matrix[0].length; // 2
2.2 数组遍历方式
Java提供了多种数组遍历方式,各有适用场景:
- 传统for循环 - 最基础也最灵活的方式
java复制for(int i=0; i<numbers.length; i++) {
System.out.println(numbers[i]);
}
- 增强for循环 - 简洁但无法获取索引
java复制for(int num : numbers) {
System.out.println(num);
}
- Arrays.stream() - Java 8+ 函数式风格
java复制Arrays.stream(numbers).forEach(System.out::println);
- while循环 - 较少使用但某些场景需要
java复制int i = 0;
while(i < numbers.length) {
System.out.println(numbers[i++]);
}
提示:在需要索引或修改数组元素时,必须使用传统for循环。仅需读取元素时,增强for循环更简洁安全。
3. List集合的长度与遍历操作
3.1 List长度获取
List集合使用size()方法获取元素数量:
java复制List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
int size = names.size(); // 2
与数组不同,List的长度是动态变化的。ArrayList内部使用数组实现,但会自动扩容;LinkedList则是基于链表实现。
3.2 List遍历方式
List除了支持数组的所有遍历方式外,还有更多选择:
- 传统for循环 - 适合需要索引的场景
java复制for(int i=0; i<names.size(); i++) {
System.out.println(names.get(i));
}
- 增强for循环 - 简洁明了
java复制for(String name : names) {
System.out.println(name);
}
- 迭代器Iterator - 安全删除元素的方式
java复制Iterator<String> it = names.iterator();
while(it.hasNext()) {
String name = it.next();
if(shouldRemove(name)) {
it.remove(); // 安全删除
}
}
- ListIterator - 双向遍历
java复制ListIterator<String> lit = names.listIterator();
while(lit.hasNext()) {
System.out.println(lit.next());
}
while(lit.hasPrevious()) {
System.out.println(lit.previous());
}
- Java 8 forEach - 函数式风格
java复制names.forEach(System.out::println);
- Stream API - 更强大的函数式操作
java复制names.stream()
.filter(name -> name.length() > 3)
.forEach(System.out::println);
实际经验:在并发环境下,使用CopyOnWriteArrayList或同步块保护遍历过程,否则可能抛出ConcurrentModificationException。
4. Set集合的长度与遍历操作
4.1 Set长度获取
Set同样使用size()方法获取元素数量:
java复制Set<Integer> numbers = new HashSet<>();
numbers.add(1);
numbers.add(2);
int size = numbers.size(); // 2
由于Set的无序性,相同的元素添加多次也只会保留一个:
java复制numbers.add(2); // 重复元素不会被添加
System.out.println(numbers.size()); // 仍然是2
4.2 Set遍历方式
Set没有索引概念,因此不支持传统for循环按索引访问。主要遍历方式有:
- 增强for循环 - 最常用方式
java复制for(Integer num : numbers) {
System.out.println(num);
}
- 迭代器Iterator - 基础方式
java复制Iterator<Integer> it = numbers.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
- Java 8 forEach - 简洁风格
java复制numbers.forEach(System.out::println);
- Stream API - 函数式操作
java复制numbers.stream()
.sorted()
.forEach(System.out::println);
注意:HashSet的遍历顺序是不确定的,如果需要有序遍历,应使用LinkedHashSet(插入顺序)或TreeSet(自然排序顺序)。
5. Map集合的长度与遍历操作
5.1 Map长度获取
Map使用size()方法获取键值对数量:
java复制Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 85);
int size = scores.size(); // 2
5.2 Map遍历方式
Map的遍历相对复杂,因为它包含键值对。主要方式有:
- 遍历键集合 - 通过keySet()
java复制for(String name : scores.keySet()) {
System.out.println(name + ": " + scores.get(name));
}
- 遍历值集合 - 通过values()
java复制for(int score : scores.values()) {
System.out.println(score);
}
- 遍历键值对集合 - 通过entrySet()(推荐方式)
java复制for(Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
- Java 8 forEach - 简洁的lambda表达式
java复制scores.forEach((name, score) ->
System.out.println(name + ": " + score));
- Stream API - 强大的函数式操作
java复制scores.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEach(entry ->
System.out.println(entry.getKey() + ": " + entry.getValue()));
性能提示:直接遍历entrySet()比先遍历keySet()再get()更高效,因为后者会导致两次哈希查找。
6. 常见问题与性能考量
6.1 遍历过程中的修改问题
在遍历集合时修改集合(非通过Iterator的remove()方法)会抛出ConcurrentModificationException:
java复制List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for(String s : list) {
if(s.equals("b")) {
list.remove(s); // 抛出异常
}
}
解决方案:
- 使用Iterator的remove()方法
- 使用Java 8的removeIf()方法
- 创建副本进行遍历
6.2 空集合与null检查
安全遍历的推荐做法:
java复制List<String> list = possiblyNullList;
if(list != null) {
for(String item : list) {
// 处理item
}
}
Java 8+更优雅的方式:
java复制Optional.ofNullable(possiblyNullList)
.orElse(Collections.emptyList())
.forEach(item -> {
// 处理item
});
6.3 性能比较
不同数据结构的遍历性能:
- 数组:O(n),最快
- ArrayList:O(n),与数组接近
- LinkedList:O(n),但实际比ArrayList慢(缓存不友好)
- HashSet:O(n),但实际比ArrayList慢(哈希冲突)
- TreeSet:O(n),但实际比HashSet慢(树遍历开销)
实际经验:在超大规模数据遍历时,优先考虑数组或ArrayList。LinkedList即使在理论上也是O(n),但因缓存未命中问题,实际性能可能差数倍。
7. 最佳实践与实用技巧
7.1 选择适当的遍历方式
根据场景选择最合适的遍历方式:
- 需要索引 → 传统for循环
- 只读遍历 → 增强for循环
- 需要过滤/转换 → Stream API
- 遍历时删除 → Iterator.remove()
- 并行处理 → parallelStream()
7.2 防御性编程技巧
- 遍历前检查null:
java复制Map<String, String> map = getPossibleNullMap();
for(Map.Entry<String, String> entry :
map != null ? map.entrySet() : Collections.emptySet()) {
// 安全遍历
}
- 使用不可变集合防止意外修改:
java复制List<String> list = Collections.unmodifiableList(originalList);
for(String item : list) { // 如果有人尝试修改会抛出异常
// 安全遍历
}
- 预分配大小优化:
java复制// 已知大小时预先分配
List<String> bigList = new ArrayList<>(expectedSize);
Map<String, String> bigMap = new HashMap<>(expectedSize);
7.3 Java 8+新特性应用
- 使用removeIf()简化条件删除:
java复制list.removeIf(item -> item.startsWith("test"));
- 使用Stream进行复杂遍历:
java复制map.entrySet().stream()
.filter(entry -> entry.getValue() > 10)
.sorted(Comparator.comparing(Map.Entry::getKey))
.forEach(entry -> process(entry));
- 并行遍历:
java复制largeList.parallelStream()
.forEach(item -> process(item));
7.4 调试与异常处理
- 遍历时打印调试信息:
java复制list.forEach(item -> {
System.out.println("Processing: " + item);
try {
process(item);
} catch(Exception e) {
System.err.println("Failed to process " + item + ": " + e.getMessage());
}
});
- 使用Objects.requireNonNull进行显式检查:
java复制list.forEach(Objects.requireNonNull(item) -> {
// item保证不为null
});
- 记录遍历耗时:
java复制long start = System.nanoTime();
list.forEach(this::process);
long duration = System.nanoTime() - start;
logger.info("Processed {} items in {} ms", list.size(), duration/1_000_000);
