👉 写代码时,总记不住 String
的 substring()
是左闭右开还是左闭右闭?
👉 想格式化日期,结果 SimpleDateFormat
一用多线程就出 bug?
👉 面试官问:“说说 Java 常用类?” 你脑子里闪过 String
、Math
,但说不完整,更别说原理了……
一、java.lang.String
:字符串王者
🎯 核心特性
- 不可变性(Immutable):一旦创建,内容不可变
- 字符序列:基于
char[]
存储(Java 9+ 用byte[]
优化) - 常量池:相同字符串只存一份,节省内存
String a = "hello";
String b = "hello";
System.out.println(a == b); // true(常量池)
✅ 常用方法
方法 | 作用 | 注意 |
---|---|---|
length() |
返回长度 | - |
charAt(int) |
获取指定位置字符 | 索引从 0 开始 |
substring(int, int) |
截取子串 | 左闭右开!substring(0,3) 取 0,1,2 |
indexOf(String) |
查找子串位置 | 找不到返回 -1 |
startsWith() / endsWith() |
判断前缀/后缀 | - |
trim() |
去除首尾空格 | - |
toUpperCase() / toLowerCase() |
大小写转换 | - |
split(String) |
分割字符串 | 返回 String[] |
replace(old, new) |
替换字符/串 | 返回新字符串 |
❌ 经典误区:字符串拼接性能
// 错误:频繁修改,创建大量中间对象
String s = "";
for (int i = 0; i < 1000; i++) {
s += i; // 每次都 new String()
}
// 正确:用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
✅ 结论:循环拼接用
StringBuilder
,单次操作用+
也可。
二、java.lang.StringBuilder
& StringBuffer
🎯 核心作用:高效字符串拼接
类 | 线程安全 | 性能 | 使用场景 |
---|---|---|---|
StringBuilder |
❌ 不安全 | ⚡️ 高 | 单线程(推荐) |
StringBuffer |
✅ 安全 | 🔼 稍低 | 多线程(极少用) |
StringBuilder sb = new StringBuilder("Hello");
sb.append(" ").append("World"); // 链式调用
sb.insert(5, " Java"); // Hello Java World
sb.delete(0, 5); // 删除 [0,5)
System.out.println(sb); // 输出: Java World
💡 建议:99% 场景用
StringBuilder
。
三、java.util.List
:有序可重复集合
🎯 核心实现类
1. ArrayList
:动态数组
- 查询快(
O(1)
),增删慢(O(n)
,涉及移动) - 内部基于
Object[]
,自动扩容(1.5倍) - 非线程安全
List<String> list = new ArrayList<>();
list.add("A");
list.add(1, "B"); // 插入
String first = list.get(0); // 查询
list.remove("A"); // 删除
2. LinkedList
:双向链表
- 增删快(
O(1)
,已知节点),查询慢(O(n)
) - 适合频繁插入/删除的场景
- 也实现了
Deque
,可作队列/栈
List<String> list = new LinkedList<>();
((LinkedList<String>) list).addFirst("A"); // 头插
((LinkedList<String>) list).addLast("B"); // 尾插
✅ 选型建议:
- 多查询、少增删 →
ArrayList
- 频繁头尾增删 →
LinkedList
四、java.util.Map
:键值对集合
🎯 核心实现类
1. HashMap
:最常用
- 基于 数组 + 链表/红黑树(JDK 8+)
- 允许 null 键和 null 值
- 非线程安全
- 无序(插入顺序不保证)
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
Integer age = map.get("Alice"); // 获取
boolean hasKey = map.containsKey("Bob");
map.remove("Alice");
2. LinkedHashMap
:有序 HashMap
- 维护插入顺序或访问顺序
- 适合做 LRU 缓存
// 按访问顺序排序(最近访问的放最后)
Map<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
3. TreeMap
:有序 Map
- 基于红黑树,按键自然排序或自定义排序
- 适合需要排序的场景
Map<String, Integer> map = new TreeMap<>(); // 按 key 字典序排序
4. ConcurrentHashMap
:线程安全 Map
- 高并发场景首选
- 分段锁(JDK 7)或 CAS + synchronized(JDK 8+)
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("count", 1);
map.get("count");
✅ 选型建议:
- 普通场景 →
HashMap
- 需要顺序 →
LinkedHashMap
或TreeMap
- 高并发 →
ConcurrentHashMap
五、java.util.Date
& java.time
:日期时间处理
🎯 老日期类(不推荐)
Date
:可读性差,方法过时SimpleDateFormat
:非线程安全!多线程共享会出错
// ❌ 危险!多线程下可能抛异常或解析错误
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// ✅ 正确:每次新建,或用 ThreadLocal,或用 DateTimeFormatter
🎯 新日期 API(Java 8+,强烈推荐)
在 java.time
包中:
类 | 用途 |
---|---|
LocalDateTime |
日期 + 时间(无时区) |
LocalDate |
仅日期 |
LocalTime |
仅时间 |
ZonedDateTime |
带时区的日期时间 |
Duration |
时间段(秒、纳秒) |
Period |
日期段(年、月、日) |
DateTimeFormatter |
格式化工具(线程安全) |
// 当前时间
LocalDateTime now = LocalDateTime.now();
// 解析字符串
LocalDateTime dt = LocalDateTime.parse("2025-08-13T10:30:00");
// 格式化
String str = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm"));
// 计算
LocalDateTime tomorrow = now.plusDays(1);
Duration duration = Duration.between(now, tomorrow);
✅ 结论:新项目一律用
java.time
,安全、清晰、功能强。
六、java.math
:精确计算
1. BigInteger
:大整数
BigInteger a = new BigInteger("12345678901234567890");
BigInteger b = new BigInteger("98765432109876543210");
BigInteger sum = a.add(b); // 加法
2. BigDecimal
:高精度浮点数(金融首选!)
BigDecimal price = new BigDecimal("19.9");
BigDecimal tax = new BigDecimal("0.05");
BigDecimal total = price.multiply(tax.add(BigDecimal.ONE));
// 保留2位小数,四舍五入
total = total.setScale(2, RoundingMode.HALF_UP);
⚠️ 绝对不要用
double
做金钱计算!System.out.println(0.1 + 0.2); // 输出 0.30000000000000004!
七、java.util.Objects
:空值工具类
String name = null;
// 判空(比 == null 更优雅)
boolean isNull = Objects.isNull(name);
boolean nonNull = Objects.nonNull(name);
// 防空指针(常用)
String displayName = Objects.requireNonNull(name, "名字不能为空");
// 生成 hashCode(处理 null)
int hash = Objects.hash(name, age);
// 比较(处理 null)
int cmp = Objects.compare("a", "b", String::compareTo);
八、高频问题 & 高分回答
Q1: String
为什么设计成不可变的?
答:
- 安全性:防止被恶意修改(如作为 HashMap 的 key);
- 线程安全:不可变对象天然线程安全;
- 缓存 hash 值:
hashCode()
只计算一次,提升性能;- 字符串常量池:实现基础,相同字符串可共享。
Q2: ArrayList
扩容机制是怎样的?
答:
- 默认容量 10;
- 扩容时,新容量 = 原容量 × 1.5;
- 使用
Arrays.copyOf()
复制数据;- 扩容是耗时操作,建议初始化时指定合理容量。
Q3: HashMap
的底层原理?
答:
JDK 8+:
- 基于 数组 + 链表/红黑树;
- 通过
hash(key)
计算桶位置;- 链表长度 > 8 且数组长度 ≥ 64 时,转为红黑树;
- 扩容时 rehash,JDK 8 优化了迁移过程。
Q4: BigDecimal
为什么能精确计算?
答:
它把浮点数拆成 unscaledValue(BigInteger) 和 scale(小数位数)。
比如19.9
存储为199
和scale=1
,所有计算基于整数,避免了二进制浮点数的精度丢失问题。
✅ 总结:一张表搞懂常用类选型
需求 | 推荐类 | 关键点 |
---|---|---|
字符串操作 | String + StringBuilder |
String 不可变,拼接用 StringBuilder |
有序集合 | ArrayList |
查询多用它,注意初始化容量 |
键值存储 | HashMap / ConcurrentHashMap |
普通用前者,并发用后者 |
日期时间 | java.time.LocalDateTime |
线程安全,API 清晰 |
金钱计算 | BigDecimal |
绝对不用 double ! |
判空工具 | Objects |
requireNonNull 防空指针 |
🔚 最后一句话
Java 常用类不是“语法糖”,而是“生产力工具”。
从String
的不可变性,到ConcurrentHashMap
的高并发设计,
每一个类背后都是对性能、安全、易用性的深度权衡。
掌握它们,你才能写出高效、健壮、专业的 Java 代码!
希望这篇能帮你系统掌握 Java 常用类!