刚入行Java开发必踩的10个坑,我刚工作那年栽了3个,帮你少走半年弯路
刚入行Java开发,写代码总觉得逻辑没错,可就是跑不起来? debug半天,结果发现是一个很低级的小问题?
我刚毕业做Java开发那一年,踩了无数这样的坑,有的坑debug整整一天才找到问题,耽误项目进度还被leader说,说多了都是泪。今天把刚入行Java开发最容易踩的10个典型坑整理出来,都是新手高频踩坑点,看看你有没有中过招。
1. ==比较字符串,踩完坑才记住要equals
这个绝对是新手排名第一的坑,刚学Java的时候分不清==和equals的区别,写判断字符串相等总用==:
java
// 错误写法,大部分情况都会返回false if (userInput == "admin") { System.out.println("登录成功"); }
==比较的是两个对象的内存地址,而equals才是比较字符串的内容,只要是比较字符串内容,一定要用equals,正确写法:
java
// 正确写法,还可以把常量iiuon.cn放前面避免空指针 if ("admin".equals(userInput)) { System.out.println("登录成功"); }
我刚入行那会,就是这个问题,登录逻辑写好了怎么都进不去,debug了两个小时才发现,说出来都脸红。
2. ArrayList遍历的时候删除元素,直接抛ConcurrentModificationException
新手想遍历集合删除符合条件的元素,习惯直接for循环或者增强for遍历,结果直接报错:
java
// 错误写法,运行直接抛异常 List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { if (s.equals("b")) { list.remove(s); } }
这是因为ArrayList用了fail-fast机制,遍历的时候修改结构,会直接抛出并发修改异常。正确的做法是用迭代器的remove方法,或者Java 8+直接用removeIf:
java
// Java 8+最简单的写法 list.removeIf(s -> s.equals("b"));
3. 日期格式工具类SimpleDateFormat是线程不安全的
很多新手图方便,把SimpleDateFormat定义成static全局变量,多线程环境下直接出问题,解析出来的日期都是错的,还不报错,找问题找到疯:
java
// 错误写法,多线程下会出问题 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat的format方法会shpivko.cn改内部的Calendar变量,多线程下共享就会出现数据错乱。现在正确的做法,要么用局部变量,要么用Java 8提供的DateTimeFormatter,线程安全还好用:
java
// Java 8+正确写法,线程安全 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
4. BigDecimal做金额计算,用double构造会精度丢失
涉及到金额计算,大家都知道要用BigDecimal,但是很多人图省事直接传double进去,结果精度直接错了:
java
// 输出结果居然是0.10000000000000000555... BigDecimal bigDecimal = new BigDecimal(0.1); System.out.println(bigDecimal);
double本身就是浮点型,不能精确表示十进制小数,传给BigDecimal也会保留精度问题,做金额计算直接错。正确做法要么用String构造,要么用BigDecimal.valueOf:
java
// 正确写法,输出就是0.1 BigDecimal bigDecimal = new BigDecimal("0.1"); System.out.println(bigDecimal);
5. 静态变量不会自动被回收,忘了清理导致内存泄漏
新手写工具类,喜欢把所有变量都定义成static,大的List或者对象一直放在静态变量里,用不完也不清理,运行时间长了,内存越来越占满,最后直接OOM:
java
// 如果这个list一直加元素不清理,堆内存只会越来越大 public static List<User> cacheList = new ArrayList<>();
静态变量的生命周期是跟整个应用绑定的,GC不会回收它引用的对象,如果不用了一定要记得把元素清理掉,或者改成局部变量,不需要全局用就别定义成static。
6. 数组转集rnrkh.cn合用Arrays.asList,调用add/remove直接抛UnsupportedOperationException
把数组转成List,大家都知道用Arrays.asList,但是转完之后想增删元素,直接抛异常:
java
List<String> list = Arrays.asList("a", "b", "c"); // 运行报错 list.add("d");
原来Arrays.asList返回的不是我们常用的java.util.ArrayList,它是Arrays内部的一个私有类,不支持修改结构操作。正确的做法是把它重新包一遍成ArrayList:
java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); list.add("d");
7. MySQL查询用了like %xxx%,索引直接失效,慢查询跑不动
新手写模糊查询,习惯直接加前后百分号,数据量小的时候没感觉,数据量上来之后,查询直接慢到超时:
sql
-- 这种写法,name上的索引直接失效,全表扫描 SELECT * FROM user WHERE name LIKE '%张三%';
如果一定要做全模糊匹配,不如用全文索引,或者业务允许的话,把百分号放后面,LIKE '张三%'这样还能走索引,速度快很多。
8. 方法返回集合,直接返回null,调用方每次都要判空,一不小心就空指针
很多新手写查询方法,查询不到结果的时候直接返回null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return null; } // ...查询逻辑 }
调用方拿到结果,一不小心忘了判空,直接list.forEach()就抛空指针了。正确做法是查询不到结果返回空集合,而不是null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } // ...查询逻辑 }
这样调用方直接遍历也不会报错,省了好多不必要的判空和空指针。
9. 锁字符串用了synchronized,同一个字符串内容居然锁不住
新手想锁同一个业务编号,直接对字符串加锁,结果发现相同内容的锁居然不生效:
java
// 错误写法,不同的字符串对象,哪怕内容相同,锁也不是同一个 public void handleOrder(String orderNo) { synchronized (orderNo) { // 处理订单逻辑 } }
因为不同的字符串对象,哪怕内容一样,内存地址也不一样,所以锁的不是同一个对象。正确做法是要么用并发包zq-xz01.cn的锁,要么把字符串缓存起来,或者直接用分布式锁,如果是本地锁也可以这样做:
java
// 简单处理,可以把字符串弱引用缓存起来,保证相同内容对应同一个对象 private static final ConcurrentHashMap<String, WeakReference<String>> LOCK_CACHE = new ConcurrentHashMap<>(); public Object getLock(String key) { WeakReference<String> ref = LOCK_CACHE.get(key); if (ref != null && ref.get() != null) { return ref.get(); } LOCK_CACHE.put(key, new WeakReference<>(key)); return key; }
10. IO流用完不关,直接导致文件句柄泄漏,占满系统资源
新手写IO操作,读文件、读写流,用完之后忘了关,时间长了系统句柄被占满,就打不开新文件了:
java
// 错误写法,异常的时候流也关不掉 FileInputStream fis = new FileInputStream("test.txt"); // 读文件逻辑 fis.close();
如果中间读逻辑xm-ty01.com.cn抛出异常,close方法就不会执行了。现在Java 7+有try-with-resources语法,自动帮你关流,根本不用手动关:
java
// 正确写法,不管有没有异常,流都会自动关闭 try (FileInputStream fis = new FileInputStream("test.txt")) { // 读文件逻辑 }
写在最后
这些坑都是我和身边刚入行的朋友反复踩过的,看着都是小问题,但是找起来真的浪费时间,刚入行的朋友把这些记下来,写代码的时候多注意,能少踩好多坑,少debug好几天。
你刚入行的时候还踩过哪些印象深刻的Java坑?欢迎在评论区补充,帮更多新手避坑。
刚入行Java开发,写代码总觉得逻辑没错,可就是跑不起来? debug半天,结果发现是一个很低级的小问题?
我刚毕业做Java开发那一年,踩了无数这样的坑,有的坑debug整整一天才找到问题,耽误项目进度还被leader说,说多了都是泪。今天把刚入行Java开发最容易踩的10个典型坑整理出来,都是新手高频踩坑点,看看你有没有中过招。
1. ==比较字符串,踩完坑才记住要equals
这个绝对是新手排名第一的坑,刚学Java的时候分不清==和equals的区别,写判断字符串相等总用==:
java
// 错误写法,大部分情况都会返回false if (userInput == "admin") { System.out.println("登录成功"); }
==比较的是两个对象的内存地址,而equals才是比较字符串的内容,只要是比较字符串内容,一定要用equals,正确写法:
java
// 正确写法,还可以把常量放前面避免空指针 if ("admin".equals(userInput)) { System.out.println("登录成功"); }
我刚入行那会,就是这个问题,登录逻辑写好了怎么都进不去,debug了两个小时才发现,说出来都脸红。
2. ArrayList遍历的时候删除元素,直接抛ConcurrentModificationException
新手想遍历集合删除符合条件的元素,习惯直接for循环或者增强for遍历,结果直接报错:
java
// 错误写法,运行直接抛异常 List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { if (s.equals("b")) { list.remove(s); } }
这是因为ArrayList用了fail-fast机制,遍历的时候修改结构,会直接抛出并发修改异常。正确的做法是用迭代器的remove方法,或者Java 8+直接用removeIf:
java
// Java 8+最简单的写法 list.removeIf(s -> s.equals("b"));
3. 日期格式工具类SimpleDateFormat是线程不安全的
很多新手图方便,把SimpleDateFormat定义成static全局变量,多线程环境下直接出问题,解析出来的日期都是错的,还不报错,找问题找到疯:
java
// 错误写法,多线程下会出问题 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat的format方法会改内部的Calendar变量,多线程下共享就会出现数据错乱。现在正确的做法,要么用局部变量,要么用Java 8提供的DateTimeFormatter,线程安全还好用:
java
// Java 8+正确写法,线程安全 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
4. BigDecimal做金额计算,用double构造会精度丢失
涉及到金额计算,大家都知道要用BigDecimal,但是很多人图省事直接传double进去,结果精度直接错了:
java
// 输出结果居然是0.10000000000000000555... BigDecimal bigDecimal = new BigDecimal(0.1); System.out.println(bigDecimal);
double本身就是浮点型,不能精确表示十进制小数,传给BigDecimal也会保留精度问题,做金额计算直接错。正确做法要么用String构造,要么用BigDecimal.valueOf:
java
// 正确写法,输出就是0.1 BigDecimal bigDecimal = new BigDecimal("0.1"); System.out.println(bigDecimal);
5. 静态变量不会自动被回收,忘了清理导致内存泄漏
新手写工具类,喜欢把所有变量都定义成static,大的List或者对象一直放在静态变量里,用不完也不清理,运行时间长了,内存越来越占满,最后直接OOM:
java
// 如果这个list一直加元素不清理,堆内存只会越来越大 public static List<User> cacheList = new ArrayList<>();
静态变量的生命周期是跟整个应用绑定的,GC不会回收它引用的对象,如果不用了一定要记得把元素清理掉,或者改成局部变量,不需要全局用就别定义成static。
6. 数组转集合用Arrays.asList,调用add/remove直接抛UnsupportedOperationException
把数组转成List,大家都知道用Arrays.asList,但是转完之后想增删元素,直接抛异常:
java
List<String> list = Arrays.asList("a", "b", "c"); // 运行报错 list.add("d");
原来Arrays.asList返回的不是我们常用的java.util.ArrayList,它是Arrays内部的一个私有类,不支持修改结构操作。正确的做法是把它重新包一遍成ArrayList:
java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); list.add("d");
7. MySQL查询用了like %xxx%,索引直接失效,慢查询跑不动
新手写模糊查询,习惯直接加前后百分号,数据量小的时候没感觉,数据量上来之后,查询直接慢到超时:
sql
-- 这种写法,name上的索引直接失效,全表扫描 SELECT * FROM user WHERE name LIKE '%张三%';
如果一定要做全模糊匹配,不如用全文索引,或者业务允许的话,把百分号放后面,LIKE '张三%'这样还能走索引,速度快很多。
8. 方法返回集合,直接返回null,调用方每次都要判空,一不小心就空指针
很多新手写查询方法,查询不到结果的时候直接返回null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return null; } // ...查询逻辑 }
调用方拿到结果,一不小心忘了判空,直接list.forEach()就抛空指针了。正确做法是查询不到结果返回空集合,而不是null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } // ...查询逻辑 }
这样调用方直接遍历也不会报错,省了好多不必要的判空和空指针。
9. 锁字符串用了synchronized,同一个字符串内容居然锁不住
新手想锁同一个业务编号,直接对字符串加锁,结果发现相同内容的锁居然不生效:
java
// 错误写法,不同的字符串对象,哪怕内容相同,锁也不是同一个 public void handleOrder(String orderNo) { synchronized (orderNo) { // 处理订单逻辑 } }
因为不同的字符串对象,哪怕内容一样,内存地址也不一样,所以锁的不是同一个对象。正确做法是要么用并发包的锁,要么把字符串缓存起来,或者直接用分布式锁,如果是本地锁也可以这样做:
java
// 简单处理,可以把字符串弱引用缓存起来,保证相同内容对应同一个对象 private static final ConcurrentHashMap<String, WeakReference<String>> LOCK_CACHE = new ConcurrentHashMap<>(); public Object getLock(String key) { WeakReference<String> ref = LOCK_CACHE.get(key); if (ref != null && ref.get() != null) { return ref.get(); } LOCK_CACHE.put(key, new WeakReference<>(key)); return key; }
10. IO流用完不关,直接导致文件句柄泄漏,占满系统资源
新手写IO操作,读文件、读写流,用完之后忘wl-xe01.com.cn了关,时间长了系统句柄被占满,就打不开新文件了:
java
// 错误写法,异常的时候流也关不掉 FileInputStream fis = new FileInputStream("test.txt"); // 读文件逻辑 fis.close();
如果中间读逻辑抛出异常,close方法就不会执行了。现在Java 7+有try-with-resources语法,自动帮你关流,根本不用手动关:
java
// 正确写法,不管有没有异常,流都会自动关闭 try (FileInputStream fis = new FileInputStream("test.txt")) { // 读文件逻辑 }
写在最后
这些坑都是我和身边刚入行的朋友反复踩过的,看着都是小问题,但是找起来真的浪费时间,刚入行的朋友把这些记下来,写代码的时候多注意,能少踩好多坑,少debug好几天。
你刚入行的时候还踩过哪些印象深刻的Java坑?欢迎在评论区补充,帮更多新手避坑。
刚入行Java开发,写代码总觉得逻辑没错,可就是跑不起来? debug半天,结果发现是一个很低级的小问题?
我刚毕业做Java开发那一年,踩了无数这样的坑,有的坑debug整整一天才找到问题,耽误项目进度还被leader说,说多了都是泪。今天把刚入行Java开发最容易踩的10个典型坑整理出来,都是新手高频踩坑点,看看你有没有中过招。
1. ==比较字符串,踩完坑才记住要equals
这个绝对是新手排名第一的坑,刚学Java的时候分不清==和equals的区别,写判断字符串相等总用==:
java
// 错误写法,大部分情况都会返回false if (userInput == "admin") { System.out.println("登录成功"); }
==比较的是两个对象的内存地址,而equals才是比较字符串的内容,只要是比较字符串内容,一定要用equals,正确写法:
java
// 正确写法,还可以把常量放前面避免空指针 if ("admin".equals(userInput)) { System.out.println("登录成功"); }
我刚入行那会,就是这个问题,登录逻辑写好了怎么都进不去,debug了两个小时才发现,说出来都脸红。
2. ArrayList遍历的时候删除元素,直接抛ConcurrentModificationException
新手想遍历集合删除符合条件的元素,习惯直接for循环或者增强for遍历,结果直接报错:
java
// 错误写法,运行直接抛异常 List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { if (s.equals("b")) { list.remove(s); } }
这是因为ArrayList用了fail-fast机制,遍历的时候修改结构,会直接抛出并发修改异常。正确的做法是用迭代器的remove方法,或者Java 8+直接用removeIf:
java
// Java 8+最简单的写法 list.removeIf(s -> s.equals("b"));
3. 日期格式工具类SimpleDateFormat是线程不安全的
很多新手图方便,把SimpleDateFormat定义成static全局变量,多线程环境下直接出问题,解析出来的日期都是错的,还不报错,找问题找到疯:
java
// 错误写法,多线程下会出问题 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat的format方法会改内部的Calendar变量,多线程下共享就会出现数据错乱。现在正确的做法,要么用局部变量,要么用Java 8提供的DateTimeFormatter,线程安全还好用:
java
// Java 8+正确写法,线程安全 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
4. BigDecimal做金额计算,用double构造会精度丢失
涉及到金额计算,大家都知道要用BigDecimal,但是很多人图省事直接传double进去,结果精度直接错了:
java
// 输出结果居然是0.10000000000000000555... BigDecimal bigDecimal = new BigDecimal(0.1); System.out.println(bigDecimal);
double本身就是浮点型,不能精确表示十进制小数,传给BigDecimal也会保留精度问题,做金额计算直接错。正确做法要么用String构造,要么用BigDecimal.valueOf:
java
// 正确写法,输出就是0.1 BigDecimal bigDecimal = new BigDecimal("0.1"); System.out.println(bigDecimal);
5. 静态变量不会自动被回收,忘了清理导致内存泄漏
新手写工具类,喜欢把所有变量都定义成static,大的List或者对象一直放在静态变量里,用不完也不清理,运行时间长了,内存越来越占满,最后直接OOM:
java
// 如果这个list一直加元素不清理,堆内存只会越来越大 public static List<User> cacheList = new ArrayList<>();
静态变量的生命周期是跟整个应用绑定的,GC不会回收它引用的对象,如果不用了一定要记得把元素清理掉,或者改成局部变量,不需要全局用就别定义成static。
6. 数组转集合用Arrays.asList,调用add/remove直接抛UnsupportedOperationException
把数组转成List,大家都知道用Arrays.asList,但是转完之后想增删元素,直接抛异常:
java
List<String> list = Arrays.asList("a", "b", "c"); // 运行报错 list.add("d");
原来Arrays.asList返回的不是我们常用的java.util.ArrayList,它是Arrays内部的一个私有类,不支持修改结构操作。正确的做法是把它重新包一遍成ArrayList:
java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); list.add("d");
7. MySQL查询用了like %xxx%,索引直接失效,慢查询跑不动
新手写模糊查询,习惯直接加前后百分号,数据量小的时候没感觉,数据量上来之后,查询直接慢到超时:
sql
-- 这种写法,name上的索引直接失效,全表扫描 SELECT * FROM user WHERE name LIKE '%张三%';
如果一定要做全模糊匹配,不如用全文索引,或者业务允许的话,把百分号放后面,LIKE '张三%'这样还能走索引,速度快很多。
8. 方法返回集合,直接返回null,调用方每次都要判空,一不小心就空指针
很多新手写查询方法,查询不到结果的时候直接返回null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return null; } // ...查询逻辑 }
调用方拿到结果,一不小心忘了判空,直接list.forEach()就抛空指针了。正确做法是查询不到结果返回空集合,而不是null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } // ...查询逻辑 }
这样调用方直接遍历也不会报错,省了好多不必要的判空和空指针。
9. 锁字符串用了synchronized,同一个字符uiqam.cn串内容居然锁不住
新手想锁同一个业务编号,直接对字符串加锁,结果发现相同内容的锁居然不生效:
java
// 错误写法,不同的字符串对象,哪怕内容相同,锁也不是同一个 public void handleOrder(String orderNo) { synchronized (orderNo) { // 处理订单逻辑 } }
因为不同的字符串对象,哪怕内容tireddg.cn一样,内存地址也不一样,所以锁的不是同一个对象。正确做法是要么用并发包的锁,要么把字符串缓存起来,或者直接用分布式锁,如果是本地锁也可以这样做:
java
// 简单处理,可以把字符串弱引用缓存起来,保证相同内容对应同一个对象 private static final ConcurrentHashMap<String, WeakReference<String>> LOCK_CACHE = new ConcurrentHashMap<>(); public Object getLock(String key) { WeakReference<String> ref = LOCK_CACHE.get(key); if (ref != null && ref.get() != null) { return ref.get(); } LOCK_CACHE.put(key, new WeakReference<>(key)); return key; }
10. IO流用完不关,直接导致文件句柄泄漏,占满系统资源
新手写IO操作,读文件、读写流,用完之后忘了关,时间长了系统句柄被占满,就打不开新文件了:
java
// 错误写法,异常的时候流也关不掉 FileInputStream fis = new FileInputStream("test.txt"); // 读文件逻辑 fis.close();
如果中间读逻辑抛出异常,close方法就不会执行了。现在Java 7+有try-with-resources语法,自动帮你关流,根本不用手动关:
java
// 正确写法,不管有没有异常,流都会自动关闭 try (FileInputStream fis = new FileInputStream("test.txt")) { // 读文件逻辑 }
写在最后
这些坑都是我和身边刚入行的朋友反复踩过的,看着都是小问题,但是找起来真的浪费时间,刚入行的朋友把这些记下来,写代码的时候多注意,能少踩好多坑,少debug好几天。
你刚入行的时候还踩过哪些印象深刻的Java坑?欢迎在评论区补充,帮更多新手避坑。
刚入行Java开发,写代码总觉得逻辑没错,可就是跑不起来? debug半天,结果发现是一个很低级的小问题?
我刚毕业做Java开发那一年,踩了无数这样的坑,有的坑debug整整一天才找到问题,耽误项目进度还被leader说,说多了都是泪。今天把刚入行Java开发最容易踩的10个典型坑整理出来,都是新手高频踩坑点,看看你有没有中过招。
1. ==比较字符串,踩完坑才记住要equals
这个绝对是新手排名第一的坑,刚学Java的时候分不清==和equals的区别,写判断字符串相等总用==:
java
// 错误写法,大部分情况都会返回false if (userInput == "admin") { System.out.println("登录成功"); }
==比较的是两个对象的内存地址,而equals才是比较字符串的内容,只要是比较字符串内容,一定要用equals,正确写法:
java
// 正确写法,还可以把常量放前面避免空指针 if ("admin".equals(userInput)) { System.out.println("登录成功"); }
我刚入行那会,就是这个问题,登录逻辑写好了怎么都进不去,debug了两个小时才发现,说出来都脸红。
2. ArrayList遍历的时候删除元素,直接抛ConcurrentModificationException
新手想遍历集合删除符合条件的元素,习惯直接for循环或者增强for遍历,结果直接报错:
java
// 错误写法,运行直接抛异常 List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { if (s.equals("b")) { list.remove(s); } }
这是因为ArrayList用了fail-fast机制,遍历的时候修改结构,会直接抛出并发修改异常。正确的做法是用迭代器的remove方法,或者Java 8+直接用removeIf:
java
// Java 8+最简单的写法 list.removeIf(s -> s.equals("b"));
3. 日期格式工具类SimpleDateFormat是线程不安全的
很多新手图方便,把SimpleDateFormat定义成static全局变量,多线程环境下直接出问题,解析出来的日期都是错的,还不报错,找问题找到疯:
java
// 错误写法,多线程下会出问题 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat的format方法会改内部的Calendar变量,多线程下共享就会出现数据错乱。现在正确的做法,要么用局部变量,要么用Java 8提供的DateTimeFormatter,线程安全还好用:
java
// Java 8+正确写法,线程安全 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
4. BigDecimal做金额计算,用double构造会精度丢失
涉及到金额计算,大家都知道要用BigDecimal,但是很多人图省事直接传double进去,结果精度直接错了:
java
// 输出结果居然是0.10000000000000000555... BigDecimal bigDecimal = new BigDecimal(0.1); System.out.println(bigDecimal);
double本身就是浮点型,不能精确表示十进制小数,传给BigDecimal也会保留精度问题,做金额计算直接错。正确做法要么用String构造,要么用BigDecimal.valueOf:
java
// 正确写法,输出就是0.1 BigDecimal bigDecimal = new BigDecimal("0.1"); System.out.println(bigDecimal);
5. 静态变量不会自动被回收,忘了清理导致内存泄漏
新手写工具类,喜欢把所有变量都定义成static,大的List或者对象一直放在静态变量里,用不完也不清理,运行时间长了,内存越来越占满,最后直接OOM:
java
// 如果这个list一直加元素不清理,堆内存只会越来越大 public static List<User> cacheList = new ArrayList<>();
静态变量的生命周期是跟整个应用绑定的,GC不会回收它引用的对象,如果不用了一定要记得把元素清理掉,或者改成局部变量,不需要全局用就别定义成static。
6. 数组转集合用Arrays.asList,调用add/remove直接抛UnsupportedOperationException
把数组转成List,大家都知道用Arrays.asList,但是转完之后想增删元素,直接抛异常:
java
List<String> list = Arrays.asList("a", "b", "c"); // 运行报错 list.add("d");
原来Arrays.asList返回的不是我们常用的java.util.ArrayList,它是Arrays内部的一个私有类,不支持修改结构操作。正确的做法是把它重新包一遍成ArrayList:
java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); list.add("d");
7. MySQL查询用了like %xxx%,索引直接失效,慢查询跑不动
新手写模糊查询,习惯直接加前后百分号,数据量小的时候没感觉,数据量上来之后,查询直接慢到超时:
sql
-- 这种写法,name上的索引直接失效,全表扫描 SELECT * FROM user WHERE name LIKE '%张三%';
如果一定要做全模糊匹配,不如用全文索引,或者业务允许的话,把百分号放后面,LIKE '张三%'这样还能走索引,速度快很多。
8. 方法返回集合,直接返回null,调用方每次都要判空,一不小心就空指针
很多新手写查询方法,查询不到结果的时候直接返回null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return null; } // ...查询逻辑 }
调用方拿到结果,一不小心忘了判空,直接list.forEach()就抛空指针了。正确做法是查询不到结果返回空集合,而不是null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } // ...查询逻辑 }
这样调用方直接遍历也不会报错,省了好多不必要的判空和空指针。
9. 锁字符串用了synchronized,同一个字符串内容居然锁不住
新手想锁同一个业务编号,直接对字符串加锁,结果发现相同内容的锁居然不生效:
java
// 错误写法,不同的字符串对象,哪怕内容相同,锁也不是同一个 public void handleOrder(String orderNo) { synchronized (orderNo) { // 处理订单逻辑 } }
因为不同的字符串对象,哪怕内容一样,内存地址也不一样,所以锁的不是同一个对象。正确做法是要么用并发包的锁,要么把字符串缓存起来,或者直接用分布式锁,如果是本地锁也可以这样做:
java
// 简单处理,可以把字符串弱引用缓存起来,保证相同内容对应同一个对象 private static final ConcurrentHashMap<String, WeakReference<String>> LOCK_CACHE = new ConcurrentHashMap<>(); public Object getLock(String key) { WeakReference<String> ref = LOCK_CACHE.get(key); if (ref != null && ref.get() != null) { return ref.get(); } LOCK_CACHE.put(key, new WeakReference<>(key)); return key; }
10. IO流用完不关,直接导致文件句柄泄漏,占满系统资源
新手写IO操作,读文件、读写流,用完之后忘了关,时间长了系统句柄被占满,就打不开新文件了:
java
// 错误写法,异常的时候流也关不掉 FileInputStream fis = new FileInputStream("test.txt"); // 读文件逻辑 fis.close();
如果中间读逻辑抛出异常,close方法就不会执行了。现在Java 7+有try-with-resources语法,自动帮你关流,根本不用手动关:
java
// 正确写法,不管有没有异常,流都会自动关闭 try (FileInputStream fis = new FileInputStream("test.txt")) { // 读文件逻辑 }
写在最后
这些坑都是我和身边刚入行的朋友反复踩过的,看着都是小问题,但是找起来真的浪费时间,刚入行的朋友把这些记下来,写代码的时候多注意,能少踩好多坑,少debug好几天。
你刚入行的时候还踩过哪些印象深刻的Java坑?欢迎在评论区补充,帮更多新手避坑。
刚入行Java开发,写代码总觉得逻辑没错,可就是跑不起来? debug半天,结果发现是一个很低级的小问题?
我刚毕业做Java开发那一年,踩了无数这样的坑,有的坑debug整整一天才找到问题,耽误项目进度还被leader说,说多了都是泪。今天把刚入行Java开发最容易踩的10个典型坑整理出来,都是新手高频踩坑点,看看你有没有中过招。
1. ==比较字符串,踩完坑才记住要equals
这个绝对是新手排名第一的坑,刚学Java的时候分不清==和equals的区别,写判断字符串相等总用==:
java
// 错误写法,大部分情况都会返回false if (userInput == "admin") { System.out.println("登录成功"); }
==比较的是两个对象的内存地址,而equals才是比较字符串的内容,只要是比较字符串内容,一定要用equals,正确写法:
java
// 正确写法,还可以把常量放前面避免空指针 if ("admin".equals(userInput)) { System.out.println("登录成功"); }
我刚入行那会,就是这个问题,登录逻辑写好了怎么都进不去,debug了两个小时才发现,说出来都脸红。
2. ArrayList遍历的时候删除元素,直接抛ConcurrentModificationException
新手想遍历集合删除符合条件的元素,习惯直接for循环或者增强for遍历,结果直接报错:
java
// 错误写法,运行直接抛异常 List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { if (s.equals("b")) { list.remove(s); } }
这是因为ArrayList用了fail-fast机制,遍历的时候修改结构,会直接抛出并发修改异常。正确的做法是用迭代器的remove方法,或者Java 8+直接用removeIf:
java
// Java 8+最简单的写法 list.removeIf(s -> s.equals("b"));
3. 日期格式工具类SimpleDateFormat是线程不安全的
很多新手图方便,把SimpleDateFormat定义成static全局变量,多线程环境下直接出问题,解析出来的日期都是错的,还不报错,找问题找到疯:
java
// 错误写法,多线程下会出问题 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat的format方法会改内部的Calendar变量,多线程下共享就会出现数据错乱。现在正确的做法,要么用局部变量,要么用Java 8提供的DateTimeFormatter,线程安全还好用:
java
// Java 8+正确写法,线程安全 private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
4. BigDecimal做金额计算,用double构造会精度丢失
涉及到金额计算,大家都知道要用BigDecimal,但是很多人图省事直接传double进去,结果精度直接错了:
java
// 输出结果居然是0.10000000000000000555... BigDecimal bigDecimal = new BigDecimal(0.1); System.out.println(bigDecimal);
double本身就是浮点型,不能精确表示十进制小数,传给BigDecimal也会保留精度问题,做金额计算直接错。正确做法要么用String构造,要么用BigDecimal.valueOf:
java
// 正确写法,输出就是0.1 BigDecimal bigDecimal = new BigDecimal("0.1"); System.out.println(bigDecimal);
5. 静态变量不会自动被回收,忘了清理导致内存泄漏
新手写工具类,喜欢把所有变量都定义成static,大的List或者对象一直放在静态变量里,用不完也不清理,运行时间长了,内存越来越占满,最后直接OOM:
java
// 如果这个list一直加元素不清理,堆内存只会越来越大 public static List<User> cacheList = new ArrayList<>();
静态变量的生命周期是跟整个应用绑定的,GC不会回收它引用的对象,如果不用了一定要记得把元素清理掉,或者改成局部变量,不需要全局用就别定义成static。
6. 数组转集合用Arrays.asList,调用add/remove直接抛UnsupportedOperationException
把数组转成List,大家都知道用Arrays.asList,但是转完之后想增删元素,直接抛异常:
java
List<String> list = Arrays.asList("a", "b", "c"); // 运行报错 list.add("d");
原来Arrays.asList返回的不是我们常用的java.util.ArrayList,它是Arrays内部的一个私有类,不支持修改结构操作。正确的做法是把它重新包一遍成ArrayList:
java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); list.add("d");
7. MySQL查询用了like %xxx%,索引直接失效,慢查询跑不动
新手写模糊查询,习惯直接加前后百分号,数据量小的时候没感觉,数据量上来之后,查询直接慢到超时:
sql
-- 这种写法,name上的索引直接失效,全表扫描 SELECT * FROM user WHERE name LIKE '%张三%';
如果一定要做全模糊匹配,不如用全文索引,或者业务允许的话,把百分号放后面,LIKE '张三%'这样还能走索引,速度快很多。
8. 方法返回集合,直接返回null,调用方每次都要判空,一不小心就空指针
很多新手写查询方法,查询不到结果的时候直接返回null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return null; } // ...查询逻辑 }
调用方拿到结果,一不小心忘了判空,直接list.forEach()就抛空指针了。正确做法是查询不到结果返回空集合,而不是null:
java
public List<User> listUserByIds(List<Long> ids) { if (ids == null || ids.isEmpty()) { return Collections.emptyList(); } // ...查询逻辑 }
这样调用方直接遍历也不会报错,省了好多不必要的判空和空指针。
9. 锁字符串用了synchronized,同一个字符串内容居然锁不住
新手想锁同一个业务编号,直接对字符串加锁,结果发现相同内容的锁居然不生效:
java
// 错误写法,不同的字符串对象,哪怕内容相同,锁也不是同一个 public void handleOrder(String orderNo) { synchronized (orderNo) { // 处理订单逻辑 } }
因为不同的字符串对象,哪怕内容一样,内存地址也不一样,所以锁的不是同一个对象。正确做法是要么用并发包的锁,要么把字符串缓存起来,或者直接用分布式锁,如果是本地锁也可以这样做:
java
// 简单处理,可以把字符串弱引用缓存起来,保证相同内容对应同一个对象 private static final ConcurrentHashMap<String, WeakReference<String>> LOCK_CACHE = new ConcurrentHashMap<>(); public Object getLock(String key) { WeakReference<String> ref = LOCK_CACHE.get(key); if (ref != null && ref.get() != null) { return ref.get(); } LOCK_CACHE.put(key, new WeakReference<>(key)); return key; }
10. IO流用完不关,直接导致文件句柄泄漏,占满系统资源
新手写IO操作,读文件、读写流,用完之后忘了关,时间长了系统句柄被占满,就打不开新文件了:
java
// 错误写法,异常的时候流也关不掉 FileInputStream fis = new FileInputStream("test.txt"); // 读文件逻辑 fis.close();
如果中间读逻辑抛出qhmysdb.cn异常,close方法就不会执行了。现在Java 7+有try-with-resources语法,自动帮你关流,根本不用手动关:
java
// 正确写法,不管有没有ophmoru.cn异常,流都会自动关闭 try (FileInputStream fis = new FileInputStream("test.txt")) { // 读文件逻辑 }
写在最后
这些坑都是我和身边刚入行的朋友反复踩过的,看着都是小问题,但是找起来真的浪费时间,刚入行的朋友把这些记下来,写代码的时候多注意,能少踩好多坑,少debug好几天。
你刚入行的时候还踩过哪些印象深刻的Java坑?欢迎在评论区补充,帮更多新手避坑。
更多推荐
所有评论(0)