一文搞懂Java异常处理

一、什么是异常处理

在Java编程中,异常处理是一种机制,用于处理程序运行时可能出现的异常情况。当程序出现异常时,程序会抛出一个异常对象,如果不加以处理,程序就会终止运行。因此,我们需要使用异常处理机制来捕获并处理这些异常,以使程序能够在出现异常时继续运行。
在Java中,异常处理主要通过try-catch语句块来实现。在try语句块中,我们可以编写可能会出现异常的代码。在catch语句块中,我们可以编写处理异常的代码,以便程序在出现异常时能够继续运行。
以下是一个简单的异常处理示例:

public class ExceptionExample {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 除数为0,会抛出ArithmeticException异常
        } catch (ArithmeticException e) {
            System.out.println("除数不能为0");
        }
    }
}

 

在上面的代码中,我们使用了try-catch语句块来处理可能出现的异常情况。在try语句块中,我们执行了一个除法运算,除数为0,会抛出一个ArithmeticException异常。在catch语句块中,我们捕获了这个异常,并输出了一条错误信息。
除了try-catch语句块,Java还提供了finally语句块,用于在try-catch语句块执行完毕后执行一些清理工作,例如关闭文件或释放资源等。以下是一个带有finally语句块的异常处理示例:

public class ExceptionExample {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 除数为0,会抛出ArithmeticException异常
        } catch (ArithmeticException e) {
            System.out.println("除数不能为0");
        } finally {
            System.out.println("程序执行完毕");
        }
    }
}

 

在上面的代码中,我们添加了一个finally语句块,该语句块中的代码会在try-catch语句块执行完毕后执行。无论是否出现异常,都会执行finally语句块中的代码。
总之,异常处理是Java编程中非常重要的一部分,它可以帮助我们处理程序运行时可能出现的异常情况,使程序更加健壮和稳定。

二、异常的分类

Java中的异常可以分为三种类型:

1. 受检异常(Checked Exception)

这种异常在编译期就可以检测到,需要在代码中显式地进行处理,否则编译不通过。一般是由外部因素所导致的,如文件不存在、网络连接中断等。
以下是一些受检异常的例子:
  • 1. FileNotFoundException(文件未找到异常):当试图打开一个不存在的文件时抛出。

  • 2. IOException(输入输出异常):当发生输入或输出错误时抛出。

  • 3. ClassNotFoundException(类未找到异常):当试图加载不存在的类时抛出。

  • 4. InterruptedException(线程中断异常):当一个线程被另一个线程中断时抛出。

  • 5. SQLException(SQL异常):当访问数据库时发生错误时抛出。

这些异常都是受检异常,必须在代码中进行处理或者声明抛出。

2. 运行时异常(Runtime Exception)

也叫非受检异常(Unchecked Exception)这种异常在编译期无法检测到,只有在程序运行时才会出现。一般是由程序逻辑错误所导致的,如数组下标越界、空指针引用等。
是指在程序运行时可能抛出的异常,这些异常不需要在代码中显式地进行捕获或声明,也不需要用throws关键字进行声明。
以下是一些Java运行时异常的例子:
  • 1. NullPointerException:当一个应用程序试图在空对象上调用方法或访问属性时,就会抛出这个异常。
  • 2. ArrayIndexOutOfBoundsException:当一个应用程序试图访问数组的无效索引时,就会抛出这个异常。
  • 3. ArithmeticException:当一个应用程序试图执行一个非法的算术操作时,就会抛出这个异常,比如除数为零。
  • 4. ClassCastException:当一个应用程序试图将一个对象强制转换为不兼容的类时,就会抛出这个异常。
  • 5. IllegalArgumentException:当一个方法接收到一个不合法的参数时,就会抛出这个异常。

需要注意的是,虽然运行时异常不需要在代码中显式地进行捕获或声明,但是在实际开发中,我们仍然需要对可能抛出的运行时异常进行处理,以保证程序的健壮性和可靠性。

3. 错误(Error)

这种异常一般是由虚拟机错误或系统错误所导致的,如内存溢出、栈溢出等。与异常不同的是,错误一般不能被处理,只能被捕获并记录日志,然后结束程序的运行。

  • 1. OutOfMemoryError:当Java虚拟机无法为对象分配所需的内存时,会抛出此错误。
  • 2. StackOverflowError:当Java虚拟机的堆栈空间不足时,会抛出此错误。
  • 3. NoClassDefFoundError:当Java虚拟机无法找到所需的类文件时,会抛出此错误。
  • 4. NoSuchMethodError:当Java虚拟机无法找到所需的方法时,会抛出此错误。
  • 5. UnsatisfiedLinkError:当Java虚拟机无法找到所需的本地库时,会抛出此错误。
  • 6. InternalError:当Java虚拟机出现内部错误时,会抛出此错误。
  • 7. SecurityException:当Java应用程序试图执行被禁止的操作时,会抛出此错误。
  • 8. AssertionError:当Java应用程序中的断言语句失败时,会抛出此错误。
  • 9. VerifyError:当Java虚拟机无法验证类文件时,会抛出此错误。
  • 10. ExceptionInInitializerError:当Java类的初始化过程中出现异常时,会抛出此错误。

三、异常的处理机制

Java的异常处理机制是一种机制,它允许开发人员在程序执行期间处理和捕获异常。当程序出现异常时,异常处理机制可以使程序更加健壮和可靠。以下是Java异常处理机制的主要步骤:
1. 抛出异常:在程序中,如果出现了异常情况,可以使用throw关键字抛出一个异常对象。例如:

throw new Exception("发生了异常");

 

2. 捕获异常:在程序中,可以使用try-catch语句块来捕获异常。try块中包含可能会抛出异常的代码,catch块中处理异常。例如:

try {
    //可能会抛出异常的代码
} catch(Exception e) {
    //处理异常
}

 

3. 处理异常:在catch块中,可以采取适当的措施来处理异常。例如,可以输出异常信息,或者重新抛出异常。例如:

try {
    //可能会抛出异常的代码
} catch(Exception e) {
    //处理异常
    System.out.println(e.getMessage()); //输出异常信息
    throw e; //重新抛出异常
}

 

4. finally块:finally块中的代码总是会被执行,无论是否发生异常。例如:

try {
    //可能会抛出异常的代码
} catch(Exception e) {
    //处理异常
} finally {
    //无论是否发生异常,这里的代码总是会被执行
}

 

Java异常处理机制使程序更加健壮和可靠,可以帮助开发人员更好地处理程序中可能发生的异常情况。

四:自定义异常

在 Java 中,我们可以自定义异常来满足特定的业务需求。自定义异常需要继承自 Java 内置的 Exception 类或其子类,然后重写构造方法。
以下是自定义异常的示例:

public class CustomException extends Exception {
    public CustomException() {
        super();
    }

    public CustomException(String message) {
        super(message);
    }

    public CustomException(String message, Throwable cause) {
        super(message, cause);
    }

    public CustomException(Throwable cause) {
        super(cause);
    }
}

 

在上面的示例中,我们定义了一个名为 CustomException 的自定义异常类,它继承自 Exception 类。该类有四个构造方法,分别对应不同的异常情况。

当我们需要抛出自定义异常时,可以像抛出内置异常一样使用 throw 关键字:

if (someCondition) {
    throw new CustomException("Something went wrong.");
}

 

在捕获自定义异常时,也可以像捕获内置异常一样使用 try-catch 块:

try {
    // some code that may throw CustomException
} catch (CustomException e) {
    // handle the exception
}

 

五、异常处理框架

Java 异常处理框架主要包括以下几个部分:

  • 1. try-catch-finally 块:用于捕获和处理异常,finally 块用于释放资源;

  • 2. throw 关键字:用于手动抛出异常;

  • 3. throws 关键字:用于声明可能会抛出的异常;

  • 4. try-with-resources 语句:用于自动关闭资源;

  • 5. Java 异常类层次结构:Java 内置了一些异常类,可以根据需要创建自定义异常类;

  • 6. 日志记录:使用日志记录工具记录异常信息。

在实际应用中,可以使用第三方框架,如 Spring、Log4j 等,来提供更方便和高效的异常处理和日志记录功能。这些框架提供了更丰富的功能,如异常处理器、异常转换器、异常映射器、日志级别控制等,可以帮助开发人员更好地管理和处理异常。

1、Java Spring 异常处理框架

(1)定义异常处理器类,该类需要实现 Spring 提供的 ExceptionHandler 接口,并实现 handleException 方法。
(2)在应用程序中配置异常处理器,可以使用 @ControllerAdvice 注解来定义全局异常处理器,也可以使用 @ExceptionHandler 注解来定义特定的异常处理器。
(3)在应用程序中抛出异常,可以使用 throw 关键字来抛出异常,也可以使用 try-catch 块来捕获异常并处理。

下面是一个使用 Java Spring 异常处理框架的示例:

(1)定义异常处理器类

@ControllerAdvice
public class GlobalExceptionHandler implements ExceptionHandler<Exception> {

    @Override
    public ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, 
            Exception ex) {
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", ex);
        mav.setViewName("error");
        return mav;
    }
}

 

(2)在应用程序中配置异常处理器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private GlobalExceptionHandler globalExceptionHandler;

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(globalExceptionHandler);
    }
}

 

(3)在应用程序中抛出异常

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
    User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    return user;
}

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {

    public UserNotFoundException(Long id) {
        super("Could not find user " + id);
    }
}

 

在上面的示例中,我们定义了一个全局异常处理器 GlobalExceptionHandler,它实现了 Spring 提供的 ExceptionHandler 接口,并实现了 handleException 方法。在应用程序中配置了该异常处理器,并将其添加到 HandlerExceptionResolver 列表中。当应用程序中抛出异常时,该异常处理器会捕获并处理异常,并返回一个包含异常信息的 ModelAndView 对象。在 getUser 方法中,我们抛出了一个自定义的异常 UserNotFoundException,并使用 @ResponseStatus 注解将其映射为 HTTP 响应状态码 404。

2、Java log4j

Java log4j是一个流行的日志处理框架,它可以帮助开发人员记录应用程序中的异常和事件,以便更好地调试和排除故障。以下是Java log4j异常处理框架的详细讲解:
1. 引入log4j库
首先,需要在项目中引入log4j库。可以通过Maven或手动下载jar包的方式引入。
2. 配置log4j
接下来,需要在项目中配置log4j。可以通过在classpath下添加log4j.properties或log4j.xml文件来配置log4j。以下是一个简单的log4j.properties配置示例:

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

 

此配置将日志输出到控制台,并记录时间、日志级别、类名、行号和消息。
3. 在代码中使用log4j
在代码中使用log4j非常简单。只需要在需要记录日志的地方,使用Logger对象进行日志记录即可。以下是一个简单的例子:

import org.apache.log4j.Logger;

public class MyClass {
    private static final Logger logger = Logger.getLogger(MyClass.class);

    public void doSomething() {
        try {
            // some code that might throw an exception
        } catch (Exception e) {
            logger.error("An error occurred", e);
        }
    }
}

 

在上面的代码中,我们使用Logger对象记录了一个错误,并将异常作为第二个参数传递给error()方法。这将导致异常的堆栈跟踪记录到日志中。
Java log4j是一个强大的日志处理框架,可以帮助开发人员更好地调试和排除故障。通过引入log4j库、配置log4j和在代码中使用Logger对象,可以轻松地记录应用程序中的异常和事件。

六、注意事项

1. 不要捕获 Exception
Exception 是所有异常的基类,它太宽泛了,可能会隐藏真正的问题。应该捕获具体的异常,例如 IOException、NullPointerException 等。


2. 不要忽略异常
如果你不知道如何处理异常,那么应该抛出它,而不是忽略它。忽略异常会导致程序出现未知的错误,可能会导致更严重的问题。


3. 使用 try-catch-finally 块
try-catch-finally 块是处理异常的标准方式。try 块中包含可能抛出异常的代码,catch 块用于捕获异常并处理它,finally 块用于执行清理操作。

4. 不要在 finally 块中抛出异常
finally 块用于执行清理操作,如果在其中抛出异常,将会覆盖之前捕获的异常。因此,应该避免在 finally 块中抛出异常。

5. 使用日志记录异常信息
在捕获异常时,应该使用日志记录异常信息,以便于排查问题。应该记录异常的类型、消息、堆栈跟踪等信息。

6. 不要使用异常控制程序流程
异常应该用于处理错误情况,而不应该用于控制程序的正常流程。如果你需要使用异常来控制程序流程,那么可能需要重构代码。

7. 使用自定义异常
如果你需要抛出特定的异常,可以考虑使用自定义异常。自定义异常可以提供更多的信息,以便于识别和处理问题。

8. 不要在构造函数中抛出异常
构造函数用于创建对象,如果在构造函数中抛出异常,可能会导致对象创建失败。因此,应该避免在构造函数中抛出异常。

9. 使用 finally 块释放资源
finally 块用于执行清理操作,应该用于释放资源,例如关闭文件、数据库连接等。

10. 不要忽略 Checked Exception
Checked Exception 是编译时异常,必须在代码中处理它们,否则编译器会报错。因此,应该处理 Checked Exception,以确保代码的健壮性。

七、异常处理的性能优化

1. 避免不必要的异常捕获和抛出:在代码中,只有在必要的情况下才应该使用异常处理机制,避免在循环或频繁调用的方法中使用异常处理。

2. 使用 try-with-resources:在使用 I/O 和数据库等资源时,应该使用 try-with-resources 语句来自动关闭资源,避免资源泄漏和性能损失。

3. 避免过多的异常捕获:在代码中,应该尽量避免过多的异常捕获,因为每次捕获异常都会有一定的性能损失。

4. 使用异常层次结构:在设计异常类时,应该尽量使用异常层次结构,避免创建过多的异常类,这样可以提高代码的可读性和性能。

5. 避免在循环中抛出异常:在循环中抛出异常会导致性能损失,应该尽量避免这种情况的发生。

6. 避免在高并发场景下使用异常:在高并发场景下,异常处理会导致性能问题,应该尽量避免使用异常处理机制,使用其他方式处理异常。

 

posted @ 2023-03-31 16:35  凉年技术  阅读(188)  评论(0编辑  收藏  举报