Java避坑案例

wen java案例 1

本文目录导读:

Java避坑案例

  1. 经典的 Integer 缓存“==”陷阱
  2. 浮点数精度丢失(金融计算大忌)
  3. 字符串 与 equals 混用
  4. ArrayList 删除元素时的“漏删”与“并发修改”
  5. 自动拆箱引发的 NullPointerException
  6. SimpleDateFormat 并发线程安全问题
  7. 使用 substringsplit 导致的内存泄漏(JDK 6 时代)
  8. switchnull 的处理
  9. ConcurrentHashMapsize() 方法误导
  10. 反射与 的比较(类型判断)
  11. 避坑三原则

Java 是一门“坑”非常多的语言,从值传递字符串常量池自动拆装箱并发集合类,每一个特性背后都藏着不少隐患。

下面我整理了 10 个高频且经典的避坑案例,每个都附带了现象、原因和解决方案:

经典的 Integer 缓存“==”陷阱

场景:比较两个数值是否相等,使用 却得到了错误结果。

public class IntegerTrap {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        Integer c = 128;
        Integer d = 128;
        System.out.println(a == b); // true
        System.out.println(c == d); // false  (经典坑)
    }
}

原因Integer-128 ~ 127 之间有缓存(IntegerCache),在这个范围内直接返回缓存对象;超出范围会 new 新对象, 比较的是内存地址。

避坑方案永远使用 equals() 比较包装类型,除非你明确知道在缓存范围内。


浮点数精度丢失(金融计算大忌)

场景:计算 0.1 + 0.2,结果不是 0.3。

double a = 0.1;
double b = 0.2;
System.out.println(a + b); // 输出: 0.30000000000000004

原因:二进制无法精确表示十进制小数(IEEE 754 标准),浮点数本身是近似值。

避坑方案涉及金额、利率、精确计算时,严禁使用 double/float,必须使用 BigDecimal,且优先使用字符串构造函数

BigDecimal c = new BigDecimal("0.1");
BigDecimal d = new BigDecimal("0.2");
System.out.println(c.add(d)); // 正确输出: 0.3

字符串 与 equals 混用

场景:比较字符串内容时使用了 。

String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2); // false (一个在常量池,一个在堆)
System.out.println(s1.equals(s2)); // true (正确比较)

避坑方案:字符串内容比较一律使用 .equals(),不要用 (除非你是在校验内存引用是否唯一)。


ArrayList 删除元素时的“漏删”与“并发修改”

场景:循环删除满足条件的元素。

// 错误写法
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    if ("B".equals(item)) {
        list.remove(i); // 删除后,后面的元素会前移,索引错位
    }
}

原因:删除元素导致索引重排,会跳过下一个元素。

避坑方案:使用迭代器的 remove()JDK 8+ 的 removeIf()

list.removeIf("B"::equals); // 一行搞定,安全高效

自动拆箱引发的 NullPointerException

场景:包装类和基础类型混合运算,包装类型为 null。

public class UnboxingTrap {
    static Integer total = null;
    public static void main(String[] args) {
        int result = total + 100; // 编译通过,但运行时报 NPE
    }
}

原因total 自动拆箱成 int 时,对 null 调用 intValue() 抛异常。

避坑方案:在使用包装类型参与运算前,必须判空,或者使用 Optional 进行流式处理。


SimpleDateFormat 并发线程安全问题

场景:多线程使用同一个 SimpleDateFormat 解析日期。

// 错误写法
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 多线程调用  sdf.parse(str) 会抛 NumberFormatException 或解析错误

原因SimpleDateFormat 不是线程安全的,内部有 Calendar 共享状态。

避坑方案

  1. 使用 DateTimeFormatter(JDK 8+,线程安全)。
  2. 或配合 ThreadLocal<SimpleDateFormat> 使用。

使用 substringsplit 导致的内存泄漏(JDK 6 时代)

场景(历史坑):截取字符串后,常驻内存。

  • JDK 6 中,substring保持原字符串的字符数组引用,如果你截取了一个很大的字符串的一小段,原大字符串无法被 GC 回收。
  • 避坑方案:在生产环境升级到 JDK 7+(已修复);或者显式使用 new String(str.substring(...)) 切断引用。

switchnull 的处理

场景:对 null 进行 switch 判断。

String str = null;
switch (str) { // 这里直接 NPE!
    case "a":
        break;
}

原因switch 在匹配前会调用 str.hashCode(),null 会抛 NPE。

避坑方案switch提前判空;或者使用 if-else + Objects.equals()


ConcurrentHashMapsize() 方法误导

场景:使用 ConcurrentHashMapsize() 获取准确数量。 原因ConcurrentHashMap 是弱一致性的并发容器,如果写入并发很高,size() 返回的是近似值,不能用于精确的业务逻辑(比如判断是否只有一个人操作)。 避坑方案:需要严格精确时加锁或使用全局计数器,不要依赖并发容器的 size() 作为硬性判断条件。


反射与 的比较(类型判断)

场景:通过反射获取 Class,比较类型。

Class clazz = Integer.class;
System.out.println(clazz == int.class); // false
System.out.println(clazz == Integer.class); // true

原因:包装类型的 Integer.class(引用类型)与 int.class(原始类型)是不同的 Class 对象。

避坑方案:判断逻辑必须区分基础类型和包装类型,不能混用。


避坑三原则

作为一个 Java 开发者,要避免踩坑,请记住这三条铁律

  1. 比较用方法:凡是比较,无论是字符串还是包装类,一律用 equals; 只用于比较原始数值和引用。
  2. 精度用 BigDecimal:凡是涉及精确计算,别用浮点。
  3. 并发用专类:凡是多线程环境,别用 SimpleDateFormat别手动加 synchronized 修复合集器,用 java.util.concurrent 包。

这些是作为 Java 开发最常踩的级别最高、修复成本最大的坑,掌握这些能有效减少日常线上 bug,如果你是新手,建议把案例 1、2、4 背下来,面试和笔试经常考。

抱歉,评论功能暂时关闭!