Loading

记录一下遇到的Java异常问题

Java异常相关问题总结

1. 运行时异常

  • 空指针异常(NullPointerException

    产生原因:

    1. 访问空对象的域
    2. 调用空对象的实例方法
    3. 访问空数组的length
    4. 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

    产生原因:

    一般是错误判断了类的继承关系。

    1. 一个类是数字类,而由于误操作,错误的将数字类向数字类转换改写成了数字类向字符串类的转换,从而产生了异常。
    2. 大部分原因是因为强制转换或者是SQL映射时发生了这个异常。

    解决方法:

    • 使用instanceof
    • 使用getclass().getName()
  • 非法参数异常(IllegalArgumentException

    产生原因:

    1. 使用枚举时:使用了不存在的名称,查询枚举

    2. 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方法都被影响,而privateprotected方法不被影响)
  • 类成员方法(优先级高于加在类上)

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. 事务的错误用法

  • privateprotected修饰的方法上使用事务
  • 底层数据库引擎不支持事务(如MysqlMylSAM)时使用事务

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-resourcesJava 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);
        }
    }
    
posted @ 2021-06-08 15:34  kosihpc  阅读(77)  评论(0编辑  收藏  举报