你有没有掉进去过这些 Exception 的“陷阱“(Part C)

简介: 你有没有掉进去过这些 Exception 的“陷阱“(Part C)

七、除了NullPointException外的其他常见异常

ConcurrentModificationException

在test包中新增测试类ConcurrentModificationExceptionTest

public class ConcurrentModificationExceptionTest {
    List<User> userList = new ArrayList<>();
    @Before
    public void before() {
        User stark = new User();
        stark.setName("stark");
        User thor = new User();
        thor.setName("thor");
        userList.add(stark);
        userList.add(thor);
    }
    @Test
    public void testModifyWhileIteratoringByFor(){
        // 直接使用for循环,触发并发修改异常
        for (User user : userList) {
            if (user.getName().equals("thor")){
                userList.remove(user);
            }
        }
    }
}
复制代码

7edfb2e4cd314eff8079bfff45a04d17_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

在使用for循环进行遍历集合同时将符合条件的元素移出集合会报并发修改异常,也就是触发了Java中的fail-fast机制。

fail-fast 机制是java集合(Collection)中的一种错误机制。 当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

要解决并发修改异常,可以使用迭代器进行遍历。增加测试方法testModifyWhileIteratoringByIterator()

@Test
public void testModifyWhileIteratoringByIterator(){
    // 直接使用迭代器
    Iterator<User> iter = userList.iterator();
    while (iter.hasNext()){
        User user = iter.next();
        if (user.getName() == "thor"){
            iter.remove();
        }
    }
}
复制代码

b82acb2555cf4b29aafdd965869e7dd7_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

next()方法一定在remove()方法之前调用,方法执行没有任何异常,虽然迭代器能够避免并发修改异常的问题,但是最好不要在遍历中删除

ClassCastException

在entity包中定义两个User的子类Admin和Employee

public class Admin extends User {
}
复制代码
public class Employee extends User {
}
复制代码

在test包下新增测试类ClassCastExceptionTest

public class ClassCastExceptionTest {
    User employee = new Employee();
    @Test
    public void testCastWithDifferentClass(){
        // 子类之间转换
        Admin admin = (Admin) employee;
    }
}
复制代码

f31405aa4c754b8c8efb71273c8a1f6f_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

两个子类之间是没有继承关系的,子类之间直接转换会抛出类型转换异常的错误,解决这类问题可以先进行类型关系判断,通过getClass().getName()来得到具体类型,再通过instanceof进行判断是否含有继承关系,如果有继承关系再进行类型转换,否则无法进行类型转换

IllegalArgumentException

日期转换时的非法参数异常

在日期转换时,如果传入的参数不对也会报错非法参数异常

@Test
public void testUser(){
    Date date = new Date();
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String simpleDate = simpleDateFormat.format(date);
    System.out.println(simpleDate);
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM");
    String format = dateFormat.format("");
    System.out.println(format);
}
复制代码

13952183852c43258cb9b91868e6f106_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

format()函数的参数是一个Object类型,所以传入String类型时不会报编译错误,但是运行时会出现IllegalArgumentException的异常

枚举查找时的非法参数异常

新建一个enums包,增加一个枚举类LoginErrorEnum,包含了三个枚举值

public enum LoginErrorEnum {
    USERNAME_OR_PASSWORD_NOT_CORRECT,
    NOT_ADMIN_ROLE,
    USERNAME_NOT_REGISTER,
}
复制代码

在test包下新增IllegalArgumentExceptionTest,测试查找一个不存在的枚举值

public class IllegalArgumentExceptionTest {
    @Test
    public void testGetValueFromEnum(){
        LoginErrorEnum passwordNotCorrect = LoginErrorEnum.valueOf("PASSWORD_NOT_CORRECT");
        System.out.println(passwordNotCorrect);
    }
}
复制代码

7826ed41245149f0889fb7cefe8bf4f4_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

枚举查找异常解决方案

第一种方式是使用try-catch这种比较通用的方式来解决枚举查找异常

@Test
public void testGetValueFromEnum(){
    try {
        LoginErrorEnum passwordNotCorrect = LoginErrorEnum.valueOf("PASSWORD_NOT_CORRECT");
        System.out.println(passwordNotCorrect);
    } catch (IllegalArgumentException e){
        System.out.println(e.getMessage());
    }
}
复制代码

071e3e38d0f44148a993f3dc75acc3f8_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

当要查找的枚举值不存在时,直接在控制台输出异常信息

第二种方式可以使用for循环遍历的方式,遍历所有的枚举值,查看是否有符合条件的枚举值,但是for循环效率较低

第三种方式可以使用Guava,首先在pom.xml文件中导入guava依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>12.0</version>
</dependency>
复制代码

新增测试方法testGetValueFromEnumWithGuava

@Test
public void testGetValueFromEnumWithGuava(){
    System.out.println(Enums.getIfPresent(LoginErrorEnum.class, "PASSWORD_NOT_CORRECT").orNull());
}
复制代码

9502262430e04fd7acbf4e634264b9eb_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

控制台输出为null,避免了非法参数异常

八、 资源关闭

资源以及资源泄露

资源有哪些?

  • 文件
  • socket连接
  • 数据库连接
  • ....

资源在使用过之后要进行关闭或者释放,如果没有释放怎会导致资源泄露的问题

try finally 关闭资源的问题

新增一个测试类HandlerResoucesTest,使用try-catch-finally关闭单个资源的代码如下

@Test
public void testCloseSingleByTryCatchFinally() throws IOException {
    String context = null;
    BufferedReader bufferedReader = new BufferedReader(new FileReader("info.txt"));
    try {
        context = bufferedReader.readLine();
        System.out.println(context);
    } catch (Exception e){
        System.out.println(e.getMessage());
    } finally {
        bufferedReader.close();
    }
}
复制代码

finally代码块中的代码无论是否出现异常都会执行,因此将资源关闭的代码放在finally中,确保操作结束后关闭资源

当try代码块中又包含另外一个资源的读取的时候,代码会变成这样

@Test
public void testCloseMultiByTryCatchFinally() throws IOException{
    String context = null;
    String message = null;
    BufferedReader bufferedReader = new BufferedReader(new FileReader("info.txt"));
    try {
        context = bufferedReader.readLine();
        BufferedReader messBufferedReader = new BufferedReader(new FileReader("message.txt"));
        try {
            message = messBufferedReader.readLine();
            System.out.println(message);
        } catch (Exception e){
            System.out.println(e.getMessage());
        } finally {
            messBufferedReader.close();
        }
        System.out.println(context);
    } catch (Exception e){
        System.out.println(e.getMessage());
    } finally {
        bufferedReader.close();
    }
}
复制代码

使用try-catch关闭多个资源时代码冗长且不易阅读

try-with-resources 解决资源泄露隐患

try-with-resources只需要声明和使用,不需要考虑关闭的问题,在try关键字后面的括号中里new一些需要自动关闭的资源。

BufferedRead从java 7开始就实现了 AutoCloseable 接口,无论try-with关闭资源是正常关闭还是异常关闭,autoClose都能关闭他们

34b958605e644b5288f1cb2d1a3da2ce_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

关闭单个资源的代码

@Test
public void testCloseSingleByTryWithResources() throws IOException {
    try(BufferedReader reader = new BufferedReader(new FileReader("info.txt"))){
        System.out.println(reader.readLine());
    }
}
复制代码

关闭多个资源的代码

@Test
public void testCloseMultiByTryWithResources() throws IOException{
    try(FileInputStream inputStream = new FileInputStream("info.txt");
        FileOutputStream outputStream  = new FileOutputStream("message.txt")) {
         byte[] buffer = new byte[100];
         int n = 0;
         while ((n = inputStream.read(buffer)) != -1){
             outputStream.write(buffer, 0, n);
         }
    }
}
复制代码

异常被覆盖的情况

如果try finally都抛出异常,finally中抛出的异常会抑制try中的异常,导致很难发现最初的异常。

在exceptions包中自定义一个异常

public class LiException extends Exception {
    public LiException() {
        super();
    }
    public LiException(String message) {
        super(message);
    }
}
复制代码

定义一个类实现AutoCloseable接口实现AutoCloseable接口,这个接口可以被try-with-resource自动关闭掉

public class LilithAutoCloseable implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("close()方法被调用");
        throw new RuntimeException("close()方法中抛出的异常");
    }
    public void work() throws LiException{
        System.out.println("work()方法被调用");
        throw new LiException("work()方法中抛出的异常");
    }
}
复制代码

增加测试方法testCloseWithAutoCloseable()

@Test
public void testCloseWithAutoCloseable() throws Exception {
    LilithAutoCloseable lilithAutoCloseable = new LilithAutoCloseable();
    try {
        lilithAutoCloseable.work();
    } finally {
        lilithAutoCloseable.close();
    }
}
复制代码

e27a85a58dd04fa794e2ecaf1cfadf63_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

work()方法中的异常被finally中的close()方法的异常覆盖掉了

使用try-with-resources则不会出现这中问题

@Test
public void testCloseWithAutoCloseableByTryWith() throws Exception {
    try(LilithAutoCloseable lilithAutoCloseable = new LilithAutoCloseable()) {
        lilithAutoCloseable.work();
    }
}
复制代码

image.png

work()方法和close()方法抛出的异常都被展示出来了


相关文章
|
4天前
|
Java
滚雪球学Java(17):探索循环控制:JavaSE中的break与continue秘技
【4月更文挑战第6天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
23 1
滚雪球学Java(17):探索循环控制:JavaSE中的break与continue秘技
|
11月前
|
消息中间件 JavaScript 小程序
减少 try catch ,可以这样干! 上
减少 try catch ,可以这样干! 上
|
11月前
|
JSON 安全 前端开发
减少 try catch ,可以这样干! 下
减少 try catch ,可以这样干! 下
|
缓存 前端开发 Java
支付宝二面:使用 try-catch 捕获异常会影响性能吗?大部分人都会答错!
支付宝二面:使用 try-catch 捕获异常会影响性能吗?大部分人都会答错!
129 0
支付宝二面:使用 try-catch 捕获异常会影响性能吗?大部分人都会答错!
|
Java 开发者 容器
【Java挠头】Java异常、捕获、处理、throw、throws等绝妙剖析
【Java挠头】Java异常、捕获、处理、throw、throws等绝妙剖析
126 0
【Java挠头】Java异常、捕获、处理、throw、throws等绝妙剖析
|
Java API 容器
你有没有掉进去过这些 Exception 的“陷阱“(Part B)
你有没有掉进去过这些 Exception 的“陷阱“(Part B)
你有没有掉进去过这些 Exception 的“陷阱“(Part B)
你有没有掉进去过这些 Exception 的“陷阱”(Part A)
你有没有掉进去过这些 Exception 的“陷阱”(Part A)
你有没有掉进去过这些 Exception 的“陷阱”(Part A)
|
小程序 Java
每日积累【Day 6】Java线程池重温 Part 3:线程池原理剖析(Part 1)
每日积累【Day 6】Java线程池重温 Part 3:线程池原理剖析(Part 1)
每日积累【Day 6】Java线程池重温 Part 3:线程池原理剖析(Part 1)
|
JSON 安全 前端开发
减少 try catch ,可以这样干!
减少 try catch ,可以这样干!
减少 try catch ,可以这样干!
|
SQL 安全 关系型数据库
故障分析 | 从 data_free 异常说起
临时表 引发的data_free异常案例
506 3
故障分析 | 从 data_free 异常说起