Java开发中常见的那些坑,你踩过几个?

摘要:大家好,我是晓凡。 作为一名Java开发者,在日常编码过程中难免会遇到各种"坑"。 有些是语法层面的问题,有些则是设计或思维上的误区。 今天我们就来盘点一下Java中最常见的20个陷阱,看
大家好,我是晓凡。 作为一名Java开发者,在日常编码过程中难免会遇到各种"坑"。 有些是语法层面的问题,有些则是设计或思维上的误区。 今天我们就来盘点一下Java中最常见的20个陷阱,看看你有没有踩过这些坑。 1. == 和 equals() 混淆 String a = new String("hello"); String b = new String("hello"); System.out.println(a == b); // false System.out.println(a.equals(b)); // true ==比较的是对象引用地址 equals()比较的是对象内容(对于String等类已重写) 2. Integer缓存陷阱 Integer a = 127; Integer b = 127; System.out.println(a == b); // true Integer c = 128; Integer d = 128; System.out.println(c == d); // false Integer在-128到127之间有缓存机制,超出范围会创建新对象。 3. 字符串拼接性能问题 // 错误做法 - 性能差 String result = ""; for (int i = 0; i < 1000; i++) { result += "a"; // 每次都创建新对象 } // 正确做法 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append("a"); } 4. 集合遍历时修改结构 // 错误做法 List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); for (String item : list) { if ("a".equals(item)) { list.remove(item); // ConcurrentModificationException } } // 正确做法 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if ("a".equals(item)) { iterator.remove(); } } 5. 忘记关闭资源 // 错误做法 FileInputStream fis = new FileInputStream("file.txt"); // 忘记关闭,可能导致资源泄露 // 正确做法 try (FileInputStream fis = new FileInputStream("file.txt")) { // 使用资源 } catch (IOException e) { // 处理异常 } 6. 异常处理不当 // 错误做法 try { // some code } catch (Exception e) { // 空的catch块,异常被默默吞掉 } // 正确做法 try { // some code } catch (SpecificException e) { logger.error("发生错误", e); // 或者重新抛出,或者适当处理 } 7. 数组和集合的toArray()陷阱 List<String> list = Arrays.asList("a", "b"); String[] array1 = list.toArray(); // 返回Object[] String[] array2 = list.toArray(new String[0]); // 返回String[] 不传参数的toArray()返回Object数组,不是原类型数组。 8. 泛型类型擦除 public class GenericTest<T> { public void test() { // T.class 是无法获取的,编译后泛型信息被擦除 } } 运行时无法获取泛型的实际类型信息。 9. 可变参数的陷阱 public static void print(Object... args) { System.out.println(args.length); } print(null); // 输出: 1 (null被视为一个元素) print(); // 输出: 0 print("a", "b"); // 输出: 2 10. switch语句忘记break int x = 1; switch (x) { case 1: System.out.println("1"); // 忘记break,会继续执行case 2 case 2: System.out.println("2"); break; } // 输出: 1 和 2 11. 浮点数比较精度问题 double a = 0.1; double b = 0.2; double c = 0.3; System.out.println(a + b == c); // false System.out.println(a + b); // 0.30000000000000004 浮点数运算存在精度误差,应该使用BigDecimal或误差范围比较。 12. 日期时间处理陷阱 // 过时的做法 Date date = new Date(2023, 12, 25); // 年份从1900开始,月份从0开始 // 推荐做法 LocalDate date = LocalDate.of(2023, 12, 25); 旧的Date API设计有问题,推荐使用新的Time API。 13. 线程安全问题 // 非线程安全 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 线程安全做法 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 很多常用类不是线程安全的,多线程环境下需要注意。 14. HashMap容量和负载因子 // 默认容量16,负载因子0.75 Map<String, String> map = new HashMap<>(); // 如果知道大概大小,可以预设容量避免频繁扩容 Map<String, String> map2 = new HashMap<>(100); 合理设置初始容量可以提高性能。 15. 空指针异常(NPE) String str = null; int length = str.length(); // NullPointerException // 防御性编程 if (str != null) { int length = str.length(); } 16. 递归没有终止条件 public int factorial(int n) { return n * factorial(n - 1); // 栈溢出Error } // 正确做法 public int factorial(int n) { if (n <= 1) return 1; // 终止条件 return n * factorial(n - 1); } 17. 忘记重写hashCode() public class Person { private String name; // 如果只重写了equals()而没有重写hashCode() // 在HashSet中会出现问题 } equals()和hashCode()必须同时重写,且保持一致性。 18. 静态变量的生命周期 public class Counter { private static int count = 0; public void increment() { count++; // 所有实例共享同一个count } } 静态变量属于类,不属于实例,所有实例共享。 19. substring()内存泄漏风险 // Java 6及以前版本存在此问题 String original = readLargeFile(); // 很大的字符串 String small = original.substring(0, 10); // 仍持有原始字符串的引用 老版本substring()会持有原字符串引用,可能导致内存泄漏。 20. 未正确实现compareTo() public class Student implements Comparable<Student> { private int age; @Override public int compareTo(Student other) { return age - other.age; // 可能整数溢出 } // 正确做法 public int compareTo(Student other) { return Integer.compare(age, other.age); } } 整数相减可能导致溢出,应使用包装类的compare方法。 你在开发中还遇到过哪些有趣的Java陷阱呢?欢迎在评论区分享!