1. Java集合框架基础概念
在Java编程中,数组和集合是我们最常使用的数据结构。数组是一种固定长度的线性数据结构,而集合框架则提供了更加灵活的数据存储方式。Java集合框架主要包含三种核心接口:List、Set和Map,每种接口都有不同的特点和适用场景。
数组和集合最基础的操作之一就是获取元素数量。对于数组,我们使用length属性;对于字符串,使用length()方法;而对于集合类,则统一使用size()方法。这个看似简单的操作在实际开发中却经常被混淆,特别是对于初学者来说。
注意:数组的length是属性而非方法,所以不需要加括号,而字符串和集合的size相关操作都是方法,必须加括号。这是Java初学者最容易犯的错误之一。
2. 数组长度获取与遍历
2.1 基本数组的长度获取
数组是Java中最基础的数据结构,其长度在创建时就已经确定且不可改变。获取数组长度的方式非常简单:
java复制int[] numbers = {10, 20, 30, 40, 50};
int length = numbers.length; // 正确:使用length属性
System.out.println("数组长度: " + length); // 输出:数组长度: 5
// 错误示例:int length = numbers.length(); // 编译错误,数组没有length()方法
对于多维数组,获取长度时需要特别注意:
java复制int[][] matrix = {{1, 2}, {3, 4}, {5, 6}};
System.out.println(matrix.length); // 输出3,表示第一维的长度
System.out.println(matrix[0].length); // 输出2,表示第二维的长度
2.2 数组遍历的几种方式
数组遍历是日常开发中最常见的操作之一,Java提供了多种遍历方式:
- 传统for循环:最基础也是最灵活的遍历方式
java复制for (int i = 0; i < numbers.length; i++) {
System.out.println("元素" + i + ": " + numbers[i]);
}
- 增强for循环:语法更简洁,但无法获取当前索引
java复制for (int num : numbers) {
System.out.println("元素: " + num);
}
- 使用Arrays工具类:Java 8及以上版本推荐
java复制Arrays.stream(numbers).forEach(System.out::println);
实际开发建议:如果需要索引信息,使用传统for循环;如果只需要元素值,优先使用增强for循环或流式API,代码更简洁。
3. 字符串长度获取与字符遍历
3.1 字符串长度获取
字符串虽然不是集合,但作为最常用的数据类型之一,其长度获取方式也值得注意:
java复制String text = "Hello, 世界";
int length = text.length(); // 注意这里是方法,不是属性
System.out.println(length); // 输出8(英文逗号和空格各占1,中文占1)
需要注意的是,length()方法返回的是字符串中UTF-16编码单元的个数,而不是实际字符数。对于包含Unicode补充字符的字符串,可能需要特殊处理:
java复制String emoji = "😊";
System.out.println(emoji.length()); // 输出2,因为emoji占用两个代码单元
System.out.println(emoji.codePointCount(0, emoji.length())); // 输出1,正确获取字符数
3.2 字符串遍历方法
字符串的字符遍历也有多种方式:
- charAt()方法:
java复制for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
System.out.println("字符" + i + ": " + c);
}
- 转换为字符数组:
java复制for (char c : text.toCharArray()) {
System.out.println(c);
}
- 使用Java 8的chars()方法:
java复制text.chars().forEach(c -> System.out.println((char)c));
性能提示:对于大量字符串处理,直接使用charAt()通常比转换为字符数组更高效,因为它避免了额外的数组创建开销。
4. List集合的长度与遍历
4.1 List基础特性
List是Java集合框架中最常用的接口之一,它具有以下特点:
- 有序集合,元素按插入顺序排列
- 允许重复元素
- 可以通过索引访问元素
- 动态扩容,无需手动管理容量
常见的List实现类包括:
- ArrayList:基于动态数组实现,随机访问快,中间插入/删除慢
- LinkedList:基于双向链表实现,随机访问慢,插入/删除快
- Vector:线程安全的ArrayList,但性能较低
4.2 获取List大小
List使用size()方法获取元素数量:
java复制List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
int size = fruits.size(); // 3
System.out.println("列表大小: " + size);
与数组不同,List的大小是动态变化的:
java复制fruits.remove("Banana");
System.out.println(fruits.size()); // 2
fruits.clear();
System.out.println(fruits.size()); // 0
4.3 List遍历方式
- 传统for循环(适合需要索引的场景):
java复制for (int i = 0; i < fruits.size(); i++) {
System.out.println("水果" + i + ": " + fruits.get(i));
}
- 增强for循环(简洁,但无索引):
java复制for (String fruit : fruits) {
System.out.println(fruit);
}
- 迭代器(可在遍历时安全删除元素):
java复制Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
String fruit = it.next();
if (fruit.equals("Banana")) {
it.remove(); // 安全删除当前元素
}
}
- Java 8 forEach:
java复制fruits.forEach(System.out::println);
// 或
fruits.forEach(fruit -> System.out.println(fruit.toUpperCase()));
性能比较:ArrayList的随机访问(get)是O(1)时间复杂度,而LinkedList是O(n)。因此,对于LinkedList应避免使用传统for循环,优先使用迭代器或增强for循环。
5. Set集合的长度与遍历
5.1 Set基础特性
Set接口代表一个不允许重复元素的集合,主要特点包括:
- 元素唯一性(基于equals()和hashCode()判断)
- 通常不保证顺序(具体取决于实现类)
- 没有索引概念,不能通过位置访问元素
常用Set实现类:
- HashSet:基于哈希表实现,无序,查询最快
- LinkedHashSet:保持插入顺序的HashSet
- TreeSet:基于红黑树实现,元素自动排序
5.2 获取Set大小
与List类似,Set也使用size()方法获取元素数量:
java复制Set<Integer> numbers = new HashSet<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(2); // 重复元素不会被添加
System.out.println(numbers.size()); // 3
5.3 Set遍历方式
由于Set没有索引,遍历方式相对有限:
- 增强for循环:
java复制for (Integer num : numbers) {
System.out.println(num);
}
- 迭代器:
java复制Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
- Java 8 forEach:
java复制numbers.forEach(System.out::println);
- 转换为数组(不推荐):
java复制for (Object num : numbers.toArray()) {
System.out.println(num);
}
重要提示:Set的遍历顺序是不确定的(除了LinkedHashSet和TreeSet)。不要依赖HashSet的元素顺序,因为它在不同JVM实现或不同运行时刻可能不同。
6. Map集合的长度与遍历
6.1 Map基础特性
Map存储键值对(key-value)数据,主要特点包括:
- 每个键唯一,值可以重复
- 基本的Map实现不保证顺序
- 常用实现类:HashMap、LinkedHashMap、TreeMap
6.2 获取Map大小
Map使用size()方法获取键值对数量:
java复制Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 85);
scores.put("Charlie", 95);
System.out.println(scores.size()); // 3
6.3 Map遍历方式
Map的遍历相对复杂,因为需要同时处理键和值:
- 遍历键集合:
java复制for (String name : scores.keySet()) {
System.out.println("Key: " + name);
}
- 遍历值集合:
java复制for (Integer score : scores.values()) {
System.out.println("Value: " + score);
}
- 遍历键值对集合(推荐):
java复制for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
- Java 8 forEach:
java复制scores.forEach((name, score) ->
System.out.println(name + ": " + score));
- 使用迭代器:
java复制Iterator<Map.Entry<String, Integer>> it = scores.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
性能提示:entrySet()遍历通常比先获取keySet()再get(key)更高效,特别是对于大型Map,因为它减少了不必要的查找操作。
7. 集合遍历的性能优化与常见问题
7.1 遍历性能比较
不同集合和不同遍历方式的性能差异很大:
-
ArrayList:
- 传统for循环最快
- 增强for循环稍慢
- forEach最慢(但有更好的可读性)
-
LinkedList:
- 迭代器和增强for循环最快
- 传统for循环非常慢(因为每次get(i)都要从头遍历)
-
HashMap:
- entrySet()遍历最快
- Java 8 forEach语法简洁但性能稍低
7.2 常见问题与解决方案
- 并发修改异常:
java复制// 错误示例:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
if (s.equals("B")) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
// 正确做法:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("B")) {
it.remove(); // 安全删除
}
}
- 空集合判断:
java复制// 不要用size() == 0判断空集合
if (collection.isEmpty()) { // 更清晰高效
// 处理空集合
}
- 遍历时修改元素:
java复制// 对于对象集合,可以直接修改元素属性
List<Person> people = ...;
for (Person p : people) {
p.setAge(p.getAge() + 1); // 可以修改对象状态
}
// 但不能替换整个元素(会引发并发修改异常)
for (Person p : people) {
p = new Person(...); // 无效,不会改变集合内容
}
7.3 Java 8 Stream API遍历
对于复杂遍历操作,可以考虑使用Stream API:
java复制// 过滤和转换
list.stream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.forEach(System.out::println);
// 并行处理大数据集
largeList.parallelStream()
.filter(...)
.forEach(...);
使用建议:对于简单遍历,传统方式更直观;对于复杂的数据处理流水线,Stream API提供了更清晰的表达方式。注意parallelStream只在数据量很大且操作耗时的情况下才有优势。
8. 实际应用中的最佳实践
-
选择合适的集合类型:
- 需要保持插入顺序 → ArrayList或LinkedHashSet
- 需要快速查找 → HashSet或HashMap
- 需要自动排序 → TreeSet或TreeMap
- 需要线程安全 → ConcurrentHashMap或CopyOnWriteArrayList
-
初始化集合大小:
java复制// 如果知道大概元素数量,预先设置容量可避免多次扩容
List<String> list = new ArrayList<>(1000); // 初始容量1000
Map<String, Integer> map = new HashMap<>(1024); // 初始容量1024
- 不可变集合:
java复制// Java 9+ 创建不可变集合
List<String> immutableList = List.of("A", "B", "C");
Set<Integer> immutableSet = Set.of(1, 2, 3);
Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);
// Java 8及以下使用Collections
List<String> unmodifiableList = Collections.unmodifiableList(list);
- 集合工具类:
java复制// 排序
Collections.sort(list);
// 二分查找
int index = Collections.binarySearch(sortedList, key);
// 填充
Collections.fill(list, "default");
// 频率统计
int freq = Collections.frequency(collection, element);
- 集合与数组转换:
java复制// 集合转数组
String[] array = list.toArray(new String[0]);
// 数组转集合(返回的List是不可变的)
List<String> list = Arrays.asList(array);
// Java 8流式转换
List<Integer> list = Arrays.stream(array)
.map(Integer::parseInt)
.collect(Collectors.toList());
在大型项目开发中,合理选择和使用集合对性能和维护性至关重要。我建议在团队中制定统一的集合使用规范,比如什么时候用ArrayList vs LinkedList,什么时候该使用不可变集合等,这样可以避免很多潜在问题。