Java开发者必须知道的20个常见陷阱

摘要:作为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()比较对象内容。

正确做法:比较字符串内容时始终使用equals()方法。

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之间有缓存,超出范围会创建新对象。

建议:比较Integer对象时使用equals()方法。

3. 字符串拼接性能问题

// 错误做法 - 性能差
String result = "";
for (int i = 0; i < 1000; i++) {
    result += "a";  // 每次都创建新StringBuilder和String对象
}

// 正确做法
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("a");
}
String result = sb.toString();

4. 集合遍历时修改结构

// 错误做法 - 会抛出ConcurrentModificationException
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");

for (String item : list) {
    if ("a".equals(item)) {
        list.remove(item);
    }
}

// 正确做法1:使用迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if ("a".equals(item)) {
        iterator.remove();
    }
}

// 正确做法2:使用Java8的removeIf
list.removeIf(item -> "a".equals(item));

5. 忘记关闭资源

// 错误做法 - 资源泄露
FileInputStream fis = new FileInputStream("file.txt");
// 使用文件流...
// 忘记调用fis.close()

// 正确做法 - 使用try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt");
     FileOutputStream fos = new FileOutputStream("output.txt")) {
    // 使用资源,自动关闭
} catch (IOException e) {
    e.printStackTrace();
}

6. 异常处理不当

// 错误做法 - 异常被吞掉
try {
    someRiskyOperation();
} catch (Exception e) {
    // 空的catch块,异常信息丢失
}

// 正确做法
try {
    someRiskyOperation();
} catch (SpecificException e) {
    logger.error("操作失败,参数: {}", someParam, e);
    throw new BusinessException("业务操作失败", e);
}

7. 数组和集合的toArray()陷阱

List<String> list = Arrays.asList("a", "b");

// 错误做法
Object[] array1 = list.toArray();  // 返回Object[]

// 正确做法
String[] array2 = list.toArray(new String[0]);
String[] array3 = list.toArray(new String[list.size()]);

8. 泛型类型擦除

// 错误做法 - 运行时无法获取泛型类型
public class GenericTest<T> {
    public void printType() {
        // 编译错误:Cannot resolve symbol 'T'
        // Class<T> clazz = T.class;
    }
}

// 解决方案:传递Class对象
public class GenericTest<T> {
    private final Class<T> type;
    
    public GenericTest(Class<T> type) {
        this.type = type;
    }
    
    public void printType() {
        System.out.println(type.getName());
    }
}

9. 可变参数的陷阱

public static void print(Object... args) {
    System.out.println(args.length);
}

print(null);        // 输出: 1 (null被视为一个元素)
print((Object) null); // 输出: 1
print();            // 输出: 0
print("a", "b");    // 输出: 2

// 正确做法 - 处理null情况
public static void safePrint(Object... args) {
    if (args == null || args.length == 0) {
        System.out.println("无参数");
        return;
    }
    System.out.println("参数个数: " + args.length);
}

10. switch语句忘记break

int day = 2;
switch (day) {
    case 1:
        System.out.println("星期一");
        // 忘记break,会继续执行
    case 2:
        System.out.println("星期二");
        break;
    case 3:
        System.out.println("星期三");
        break;
}
// 输出: 星期二 (期望只输出星期二,但实际输出星期一和星期二)

// 正确做法 - 每个case都要有break
switch (day) {
    case 1:
        System.out.println("星期一");
        break;
    case 2:
        System.out.println("星期二");
        break;
    default:
        System.out.println("其他");
        break;
}

// Java 14+ 更好的写法
String dayName = switch (day) {
    case 1 -> "星期一";
    case 2 -> "星期二";
    default -> "其他";
};

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

// 正确做法1:使用误差范围
boolean isEqual = Math.abs((a + b) - c) < 0.000001;

// 正确做法2:使用BigDecimal
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
BigDecimal bd3 = new BigDecimal("0.3");
System.out.println(bd1.add(bd2).equals(bd3));  // true

12. 日期时间处理陷阱

// 错误做法 - 使用过时的Date API
Date date = new Date(2023, 12, 25);  // 年份从1900开始,月份从0开始
System.out.println(date.getYear());  // 123 (2023-1900)

// 正确做法 - 使用java.time包
LocalDate date = LocalDate.of(2023, 12, 25);
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

13. 线程安全问题

// 错误做法 - SimpleDateFormat不是线程安全的
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

// 线程1
new Thread(() -> {
    System.out.println(sdf.format(new Date()));
}).start();

// 线程2 - 可能产生不可预期的结果
new Thread(() -> {
    System.out.println(sdf.format(new Date()));
}).start();

// 正确做法1:每个线程使用独立的实例
new Thread(() -> {
    SimpleDateFormat localSdf = new SimpleDateFormat("yyyy-MM-dd");
    System.out.println(localSdf.format(new Date()));
}).start();

// 正确做法2:使用ThreadLocal
private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// 正确做法3:使用java.time
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println(LocalDate.now().format(formatter));

14. HashMap容量设置

// 如果知道大概的元素数量,预设容量可以提高性能
int expectedSize = 1000;

// 错误做法 - 默认容量16,可能频繁扩容
Map<String, String> map1 = new HashMap<>();

// 正确做法 - 预设初始容量
Map<String, String> map2 = new HashMap<>(expectedSize);

// 更精确的做法 - 考虑负载因子
int initialCapacity = (int) (expectedSize / 0.75f) + 1;
Map<String, String> map3 = new HashMap<>(initialCapacity);

15. 空指针异常防护

// 错误做法
String str = null;
int length = str.length();  // NullPointerException

// 正确做法1:显式检查
if (str != null) {
    int length = str.length();
}

// 正确做法2:使用Optional
Optional<String> optionalStr = Optional.ofNullable(str);
int length = optionalStr.map(String::length).orElse(0);

// 正确做法3:Java 14+ 使用Objects.requireNonNull
String safeStr = Objects.requireNonNull(str, "字符串不能为null");

// 正确做法4:使用工具方法
public static int safeLength(String str) {
    return str == null ? 0 : str.length();
}

16. 递归没有终止条件

// 错误做法 - 无限递归导致栈溢出
public int factorial(int n) {
    return n * factorial(n - 1);  // StackOverflowError
}

// 正确做法 - 添加终止条件
public int factorial(int n) {
    if (n <= 1) {
        return 1;  // 终止条件
    }
    return n * factorial(n - 1);
}

// 更好的做法 - 使用迭代避免栈溢出
public int factorialIterative(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

17. 忘记重写hashCode()

// 错误做法 - 只重写equals(),不重写hashCode()
public class Person {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
    
    // 忘记重写hashCode()
}

// 正确做法 - equals()和hashCode()必须同时重写
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

18. 静态变量的生命周期

public class Counter {
    private static int count = 0;  // 静态变量,所有实例共享
    
    public void increment() {
        count++;
    }
    
    public static int getCount() {
        return count;
    }
}

// 使用示例
Counter c1 = new Counter();
Counter c2 = new Counter();
c1.increment();  // count变为1
c2.increment();  // count变为2
System.out.println(Counter.getCount());  // 输出2,不是1

19. 字符串操作性能

// 错误做法 - 在老版本Java中substring可能导致内存泄漏
String largeString = readLargeString();  // 假设返回很大的字符串
String small = largeString.substring(0, 10);

// 在Java 7u6之前,small会持有largeString的引用
// 正确做法 - 如果需要释放大字符串,显式创建新字符串
String small = new String(largeString.substring(0, 10));

20. compareTo()实现问题

// 错误做法 - 可能整数溢出
public class Student implements Comparable<Student> {
    private int age;
    
    @Override
    public int compareTo(Student other) {
        return age - other.age;  // 如果age接近Integer极值,可能溢出
    }
}

// 正确做法
@Override
public int compareTo(Student other) {
    return Integer.compare(age, other.age);
}

// 或者对于多个字段的比较
@Override
public int compareTo(Student other) {
    int ageCompare = Integer.compare(age, other.age);
    if (ageCompare != 0) {
        return ageCompare;
    }
    return name.compareTo(other.name);
}

总结

这些常见的Java陷阱涵盖了从基础语法到高级特性的各个方面。避免这些陷阱的关键在于:

  1. 理解原理:不只是记住语法,要理解背后的机制

  2. 代码审查:多人review代码,互相学习

  3. 单元测试:编写全面的测试用例

  4. 持续学习:关注Java新特性和最佳实践

记住这些陷阱并采用正确的做法,将帮助你写出更健壮、更高效的Java代码。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13191