你有没有掉进去过这些 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()方法抛出的异常都被展示出来了


相关文章
|
9月前
|
Java 测试技术
血的教训--如何正确使用线程池submit和execute方法
血的教训--如何正确使用线程池submit和execute方法
79 1
|
9月前
|
Java
请解释Java中的异常处理机制,并给出使用try-catch块的示例。
请解释Java中的异常处理机制,并给出使用try-catch块的示例。
154 0
|
3月前
|
Java 编译器 测试技术
深入探讨:try-catch对Java性能的影响
在Java编程中,异常处理是一个不可或缺的部分。使用`try-catch`块可以捕获和处理异常,防止程序崩溃。然而,关于`try-catch`对性能的影响,开发者们持有不同的观点。本文将深入探讨`try-catch`对Java程序性能的影响,并提供一些最佳实践。
60 5
|
3月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
115 2
|
6月前
|
Java
【Java基础面试三十九】、 finally是无条件执行的吗?
这篇文章解释了Java中的finally块的特性,即无论是否发生异常或执行了return语句,finally块都会无条件执行,除非使用System.exit()退出虚拟机。
|
6月前
|
监控 安全 Swift
减少 Try-Catch,可以这样干!
【8月更文挑战第5天】在软件开发中,try-catch 语句是处理异常的重要机制,但过度使用往往会导致代码臃肿、逻辑复杂且难以维护。今天,我们就来探讨几种有效减少 try-catch 使用的方法,让你的代码更加简洁、高效。
147 4
|
8月前
|
安全 Java 程序员
💥JAVA世界里的“拆弹专家”:try-catch-finally如何拯救你的代码?
【6月更文挑战第18天】Java异常处理的关键是`try-catch-finally`,它确保程序在遇到错误时不崩溃。例如,在文件操作中,`try`块尝试读/写文件,`catch`捕获如FileNotFoundException或IOException,打印错误信息,而`finally`确保资源释放。通过这种方式,代码能优雅处理异常,增强健壮性。
56 0
|
9月前
|
安全 Java 程序员
代码救火队:try-catch-finally带你走出异常困境
代码救火队:try-catch-finally带你走出异常困境
68 0
因为一道题,我把 try-catch-finally 的细节都整理了一遍(1500字)
原因:return i++; 在内部是可以分为三步,① int tmp = i; ② i += 1; ③ return tmp;
109 0
因为一道题,我把 try-catch-finally 的细节都整理了一遍(1500字)
|
Java 开发者 容器
【Java挠头】Java异常、捕获、处理、throw、throws等绝妙剖析
【Java挠头】Java异常、捕获、处理、throw、throws等绝妙剖析
176 0
【Java挠头】Java异常、捕获、处理、throw、throws等绝妙剖析

热门文章

最新文章