「灵魂拷问」 为什么数组用length,字符串用length(),集合却用size()?这看似简单的三个方法背后,藏着Java设计者的哪些深意?本文将带你深入源码,解密这三个"尺寸测量器"的本质区别!
🎯 三者的本质区别(速查表)
特性 | 数组.length | String.length() | Collection.size() |
---|---|---|---|
数据类型 | 属性 | 方法 | 方法 |
可变性 | 固定不变 | 字符串不可变 | 动态变化 |
时间复杂度 | O(1) | O(1) | 多数集合O(1) |
底层实现 | JVM直接支持 | char数组长度 | 集合内部计数器 |
空值处理 | 不会为null | NPE风险 | 可能返回0 |
适用场景 | 基础数组 | 字符串 | 所有集合类 |
📦 数组的length属性:最原始的长度测量
1.1 基本用法
int[] numbers = new int[5];
System.out.println(numbers.length); // 输出5
String[][] matrix = new String[3][4];
System.out.println(matrix.length); // 3(第一维长度)
System.out.println(matrix[0].length);// 4(第二维长度)
1.2 核心特性
- 编译期确定:数组长度在初始化时确定,无法修改
- 内存直接访问:JVM通过数组对象头直接获取长度
- 多维数组的陷阱:每个维度的length独立计算
1.3 底层原理(HotSpot源码解析)
// arrayOop.hpp
class arrayOopDesc : public oopDesc {
...
int length() const {
return *(int*)(((char*)this) + length_offset_in_bytes());
}
...
}
数组对象在内存中的布局:
| 对象头 | 长度(4字节) | 元素数据... |
📜 String的length()方法:字符序列的守护者
2.1 基础应用
String str = "Hello世界";
System.out.println(str.length()); // 输出7(注意中文占1个char!)
String empty = "";
System.out.println(empty.length()); // 0
String nullStr = null;
System.out.println(nullStr.length()); // 抛出NullPointerException
2.2 关键知识点
- Unicode代码单元:一个char不一定等于一个Unicode字符
- 不可变性:字符串长度在创建后不可改变
- 内存优化:JDK9后使用byte[]存储(LATIN1/UTF-16编码)
2.3 源码追踪(JDK17)
public final class String {
private final byte[] value;
private final byte coder; // 0-LATIN1, 1-UTF16
public int length() {
return value.length >> coder; // 妙用位运算!
}
}
编码方式对长度计算的影响:
- LATIN1编码:每个字符1字节,直接返回数组长度
- UTF-16编码:右移1位(相当于除以2)
🗃 集合的size()方法:动态容器的智能标尺
3.1 典型用法
List<String> list = new ArrayList<>();
System.out.println(list.size()); // 0
list.add("Java");
list.add("Python");
System.out.println(list.size()); // 2
Map<Integer, String> map = new HashMap<>();
map.put(1, "One");
System.out.println(map.size()); // 1
3.2 实现原理对比
集合类型 | size()实现原理 | 时间复杂度 |
---|---|---|
ArrayList | 维护size变量 | O(1) |
LinkedList | 遍历节点计数(JDK8优化) | O(n) → O(1) |
ConcurrentHashMap | 分段计数求和 | O(1) |
PriorityQueue | 维护size变量 | O(1) |
3.3 线程安全陷阱
// 错误示例!
List<String> unsafeList = new ArrayList<>();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
if (unsafeList.size() < 500) { // 这里size()可能被其他线程修改
unsafeList.add("data");
}
});
}
正确做法:使用并发集合或同步块
💥 高频踩坑案例集锦
4.1 空指针三连击
// 案例1:未初始化的数组
int[] arr = null;
System.out.println(arr.length); // NullPointerException
// 案例2:未初始化的字符串
String s = null;
System.out.println(s.length()); // NullPointerException
// 案例3:集合size()的NPE保护
List<String> list = null;
System.out.println(list.size()); // NullPointerException
4.2 多维数组的迷宫
int[][] matrix = new int[3][];
matrix[0] = new int[2];
matrix[1] = new int[5];
System.out.println(matrix.length); // 3
System.out.println(matrix[0].length); // 2
System.out.println(matrix[2].length); // NullPointerException
4.3 集合的"薛定谔的size"
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("B")) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
正确姿势:使用迭代器的remove方法
🚀 性能优化指南
5.1 数组length的缓存技巧
// 优化前
for (int i = 0; i < array.length; i++) { ... }
// 优化后(适用于超长数组)
int len = array.length;
for (int i = 0; i < len; i++) { ... }
实测性能提升约5%(JMH测试)
5.2 字符串长度的预处理
// 需要频繁调用length()时
String bigString = "..."; // 超长字符串
int len = bigString.length();
for (int i = 0; i < len; i++) {
// 避免每次循环都调用length()
}
5.3 集合size()的智能判断
// 低效写法
if (collection.size() > 0) { ... }
// 高效写法
if (!collection.isEmpty()) { ... }
原理:isEmpty()通常直接返回size() == 0,但部分集合有优化实现
📚 高频面试题解析
Q1:为什么数组用属性而字符串用方法?
参考回答:
- 数组是Java的基础数据结构,length作为属性可以提升访问效率
- 字符串的length需要封装计算逻辑(如JDK9后的压缩存储)
- 符合面向对象设计原则(封装变化)
Q2:如何正确获取集合的实时大小?
参考答案:
- 对非并发集合:需要同步块保护
- 推荐使用并发集合的size()方法
- 注意size()的时间复杂度(如LinkedList在JDK8前后的差异)
Q3:字符串length()是否等于字符数组长度?
参考答案:
- 对于普通字符串:是
- 包含Unicode代理对时:可能不等
String emoji = "😀";
System.out.println(emoji.length()); // 输出2
System.out.println(emoji.codePointCount(0, emoji.length())); // 输出1
🌟 总结与最佳实践
- 数组.length:固定不变,直接访问
- String.length():注意编码影响,防范NPE
- Collection.size():关注线程安全,优选isEmpty()
黄金法则:
- 操作数组前检查null
- 字符串处理注意Unicode
- 集合size()要配合同步机制
「实战建议」 在IDEA中安装"SonarLint"插件,可以自动检测length相关常见错误!
✍️ 本文持续修订于:2023-08-20
🏷️ 相关标签:#Java基础 #集合框架 #数组 #字符串 #性能优化
🔗 延伸阅读:
📢 互动话题:你在开发中遇到过哪些关于length的"坑"?欢迎在评论区分享你的故事!如果觉得本文有帮助,请点赞⭐收藏支持作者~ 😊