Java开发常见坑整理:21个高频且隐蔽的陷阱及避坑实践
Java 开发中踩坑是常态,很多看似正确的代码在特定场景下会暴露出严重问题。本文为你整理了一份高频且隐蔽的"坑王"清单,并附上详细的避坑建议。
一、集合操作中的致命陷阱
1. Arrays.asList() 返回的不是 ArrayList
// 错误示例
List<Integer> list = Arrays.asList(1, 2, 3);
list.add(4); // ❌ 抛出 UnsupportedOperationException原因:返回的是内部类 ArrayList,不支持增删操作。
避坑:用 new ArrayList<>(Arrays.asList(1,2,3)) 包一层。
2. 遍历时删除元素
// 错误示例
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if (s.equals("b")) list.remove(s); // ❌ ConcurrentModificationException
}避坑:使用 Iterator.remove() 或 removeIf()(Java 8+)。
3. HashMap 容量初始值被忽略
// 若 expectedSize = 1000,实际容量为 1024,扩容阈值 ≈ 1024*0.75=768,未到1000就会扩容
Map map = new HashMap(1000);
// 正确:除以0.75 + 1
Map map = new HashMap((int)(expectedSize / 0.75f) + 1);二、并发与多线程
4. synchronized 锁错对象
// 错误:锁的是 Integer 常量池对象(-128~127 复用)
private Integer count = 0;
synchronized(count) { count++; }
// 错误:锁的是字符串常量池("lock" 可能被其他代码共用)
synchronized("lock") { }避坑:锁用 private final Object lock = new Object()。
5. volatile 不能保证原子性
volatile int count = 0;
count++; // ❌ 不是原子操作(读-改-写)避坑:用 AtomicInteger 或 synchronized。
6. SimpleDateFormat 线程不安全
// 作为 static 成员变量在多线程下会报错或产生脏数据
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");避坑:用 ThreadLocal 包装,或用 Java 8 的 DateTimeFormatter(线程安全)。
三、字符串与数值
7. 浮点数精度丢失
System.out.println(0.1 + 0.2); // 0.30000000000000004避坑:金融计算用 BigDecimal,且必须用 String 构造器:
new BigDecimal("0.1").add(new BigDecimal("0.2"));8. Integer 缓存陷阱(-128 ~ 127)
Integer a = 100, b = 100;
a == b // true(缓存命中)
Integer c = 200, d = 200;
c == d // false(new 了两个对象)避坑:对象比较一律用 equals()。
9. 字符串拼接的 null 灾难
String s = null;
System.out.println(s + "abc"); // "nullabc"(不是 NPE)避坑:拼接前判空,或用 String.valueOf()。
四、异常与流处理
10. finally 中返回值吞掉异常
try {
throw new RuntimeException();
} finally {
return; // ❌ 异常被吞掉
}避坑:finally 中不要写 return。
11. 资源未关闭
InputStream is = new FileInputStream("xxx");
// 忘记在 finally 中 close()避坑:使用 try-with-resources(Java 7+)。
12. 捕获 Exception 但未处理,导致问题被掩盖
try {
// 业务代码
} catch (Exception e) {
// 空catch 或只打印日志不抛——错误被静默吞掉
}五、数据库与事务
13. 事务失效场景(Spring)
方法非 public
同类内部调用(this.method())
异常被 try-catch 吞掉
数据库引擎不支持事务(如 MyISAM)
14. save() 与 saveAndFlush() 的坑
save() 在事务提交前不执行 SQL,若后续发生异常,会插入预期外的数据(主键已被占用)。避坑:明确是否需要立即刷出。
六、Spring Boot 常见配置坑
15. @Value 注入静态变量失效
@Value("${xxx}")
private static String xxx; // ❌ 永远是 null避坑:用 @Value 加 @PostConstruct 赋值给静态变量,或使用 @ConfigurationProperties。
16. 配置文件中的 - 与驼峰映射
my.user-name=xxx # 对应 userName
my.userName=xxx # 有时会被解析为两个不同值七、日期时间(Java 8 之前)
17. Calendar 的月份从 0 开始
Calendar.set(2026, 9, 9); // 实际是 10月9日(9代表10月)避坑:Java 8 后用 LocalDate.of(2026, 9, 9) 更清晰。
18. java.sql.Date 丢失时间
new java.sql.Date(System.currentTimeMillis()); // 只有日期,时分秒被截断八、其他高频坑
19. 使用 == 比较 BigDecimal
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
a.equals(b) // false(比较 scale)
a.compareTo(b) == 0 // true(推荐)20. 泛型擦除导致的类型强制转换异常
List list = new ArrayList();
list.add("abc");
list.add(123); // 编译通过,运行时强转失败21. 数组转 List 后操作原始数组
String[] arr = {"a","b"};
List list = Arrays.asList(arr);
arr[0] = "c"; // list 也变了,因为 list 是原数组的视图实用避坑方法论
对象比较:全用 equals(除了基本类型)
日期时间:Java 8 用 LocalDateTime,弃用 Date/Calendar
集合初始化:明确容量,避免频繁扩容
并发:优先使用 java.util.concurrent 包下的工具
日志:用 SLF4J,占位符形式 log.info("id={}", id),避免字符串拼接
空指针防御:Optional + 方法返回值明确标注 @Nullable/@NonNull
希望这份详细的Java开发常见坑整理能帮助你在开发过程中避免踩坑,提高代码质量和系统稳定性。记住这些经验教训,让你的Java开发更加稳健!
评论区