记录一下遇到的Java异常问题
Java异常相关问题总结
1. 运行时异常
-
空指针异常(
NullPointerException
)产生原因:
- 访问空对象的域
- 调用空对象的实例方法
- 访问空数组的length
throw null
解决方法:
-
使用
if
判断 -
使用
Optional
容器(Java 8
)
-
并发修改异常(
ConcurrentModificationException
)产生原因:
// 在执行以下代码时,就会抛出 ConcurrentModificationException 异常 @Test public void testConcurrentModificationException() { ArrayList<Integer> list = new ArrayList<>(); list.add(2); Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ Integer integer = iterator.next(); if(integer==2) { list.remove(integer); } } } // 出现此现象的根本原因是:调用list.remove()方法导致modCount和expectedModCount的值不一致。 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
注:像使用for-each进行迭代实际上也会出现这种问题。
解决办法:
-
使用
Collection.removeIf
方法(推荐)@Test public void solveConcurrentModificationException1() { ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.removeIf(integer -> integer == 2); }
-
使用
Iterator.remove()
方法(代码繁琐)@Test public void solveConcurrentModificationException2() { ArrayList<Integer> list = new ArrayList<>(); list.add(2); Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ Integer integer = iterator.next(); if(integer==2) { iterator.remove(); } } System.out.println(list); }
-
使用
Stream.filter()
方法@Test public void solveConcurrentModificationException3() { ArrayList<Integer> list = new ArrayList<>(); list.add(2); ArrayList<Integer> collect = list.stream().filter(i -> i != 2) .collect(Collectors.toCollection(ArrayList::new)); System.out.println(collect); }
-
使用并发容器
CopyOnWriteArrayList
(结合业务需求)@Test public void solveConcurrentModificationException4() { CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(); list.add(2); Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ Integer integer = iterator.next(); if(integer==2) { list.remove(integer); } } System.out.println(list); }
-
-
类型转换异常(
ClassCastException
)产生原因:
一般是错误判断了类的继承关系。
- 一个类是数字类,而由于误操作,错误的将数字类向数字类转换改写成了数字类向字符串类的转换,从而产生了异常。
- 大部分原因是因为强制转换或者是SQL映射时发生了这个异常。
解决方法:
- 使用
instanceof
- 使用
getclass().getName()
-
非法参数异常(
IllegalArgumentException
)产生原因:
-
使用枚举时:使用了不存在的名称,查询枚举
-
LocalDateTime
使用SimpleDateFormat
进行格式化,如:@Test public void testIllegalArgumentException() { LocalDateTime now = LocalDateTime.now(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = simpleDateFormat.format(now); System.out.println(date); }
解决办法:
对于枚举产生的异常:
- 直接
try catch
(代码繁琐,但是最简单) - 利用
values
方法遍历(代码简单,但是效率低) - 静态
map
索引(效率高,但可能引发NPE
) - 使用
Guava
中的方法(需要引入外部依赖)
对于
LocalDateTime
格式化产生的异常:-
使用
DateTimeFormatter.ofPattern
而不是SimpleDateFormat
@Test public void solveIllegalArgumentException() { LocalDateTime now = LocalDateTime.now(); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String date = dateTimeFormatter.format(now); System.out.println(date); }
-
2. Spring事务
Spring
事务分为:编程式事务和声明式事务。其中,编程式事务侵入性强,样板代码太多,已经不流行了。这里主要介绍基于注解的声明式事务。
1. @Transactional
位置
- 接口(少见,
Spring
也不推荐这样做) - 类(类中所有
public
方法都被影响,而private
及protected
方法不被影响) - 类成员方法(优先级高于加在类上)
2. @Transactional
常用属性
isolation
- 事务的隔离级别,默认值是底层所用数据库的隔离级别timeout
- 事务的超时时间,超时就回滚rollbackFor
- 指定可以触发回滚的异常类的集合noRollbackFor
- 指定不能触发回滚的异常类的集合
3. 事务失效的情况
-
主动捕获了异常
用编程式事务的方式,手动标记回滚。
-
方法抛出的不是运行时异常
解决方法:使用
rollbackFor
@Transactional(rollbackFor = Exception.class) public void insert() { try { dao.insert(entity); } catch (Exception e) { log.error(e.getMessage()); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
-
未标注
@Transactional
的方法A,调用了标记@Transactional
的方法B,那么调用A时,事务不生效。解决方法:尝试将
@Transactional
标记到A上而不是B上。
4. 事务的错误用法
- 在
private
和protected
修饰的方法上使用事务 - 底层数据库引擎不支持事务(如
Mysql
的MylSAM
)时使用事务
3. 正确关闭资源
资源的种类有很多,比如:流、文件、Socket
连接等等,在使用资源后需要正确关闭资源。
关闭资源的方式:
-
try-finally
方式(传统方式)@Test public void testResources() throws IOException { InputStream inputStream = null; try { inputStream = new FileInputStream("path"); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (inputStream != null) { inputStream.close(); } } }
劣势:
try
块和finally
块中都可能抛出异常,造成Debug
困难- 关闭多个资源时,代码嵌套严重,容易写错代码
- 不熟悉资源相关
API
时,容易忘记关闭资源
-
try-with-resources
(Java 7
新特性)// public interface Closeable extends AutoCloseable // public abstract class InputStream implements Closeable // InputStream流可以自动关闭 @Test public void testResources1() throws IOException { try (InputStream inputStream = new FileInputStream("path")) { System.out.println(inputStream); } catch (FileNotFoundException e) { e.printStackTrace(); } }
-
AutoCloseable
接口实现了该接口的类都可以被当成一个可以自动关闭的资源类,可以在
try(...)
中声明并自动关闭。 -
消除了很多样板式代码,代码更加简洁
-
不会出现
try
块中抛出异常,被finally
块中抛出的异常抑制的情况
-
4. Spring Web全局异常处理
-
@RestControllerAdvice
对
Controller
的功能进行增强,影响所有的@ResquestMapping
标记的方法。@RestControllerAdvice @Slf4j @CrossOrigin @RestController @RequestMapping("/api") public class LoginAndRegisterController { @PostMapping("/login") public Msg login(@RequestBody LoginUser loginUser) { return new Msg(); } }
-
@ExceptionHandler
配合
Advice
使用,感知被抛出的异常,自定义统一的处理逻辑。@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(TokenException.class) // @ResponseStatus(HttpStatus.BAD_REQUEST) public Msg handleException(TokenException e) { return new Msg(e.getStatus(), e.getMessage(), null); } }