大家好,我是晓凡。
作为一名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 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 {
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陷阱呢?欢迎在评论区分享!
