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)); // true12. 日期时间处理陷阱
// 错误做法 - 使用过时的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,不是119. 字符串操作性能
// 错误做法 - 在老版本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陷阱涵盖了从基础语法到高级特性的各个方面。避免这些陷阱的关键在于:
理解原理:不只是记住语法,要理解背后的机制
代码审查:多人review代码,互相学习
单元测试:编写全面的测试用例
持续学习:关注Java新特性和最佳实践
记住这些陷阱并采用正确的做法,将帮助你写出更健壮、更高效的Java代码。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!