1. 概述

try/catch 块有时会导致代码冗长甚至结构混乱。本文将介绍 NoException 库,它提供简洁高效的异常处理器,能显著简化异常处理代码。

2. Maven 依赖

pom.xml 中添加 NoException 依赖:

<dependency>
    <groupId>com.machinezoo.noexception</groupId>
    <artifactId>noexception</artifactId>
    <version>1.1.0</version>
</dependency>

3. 标准异常处理

先看一个常见的异常处理模式:

private static Logger logger = LoggerFactory.getLogger(NoExceptionUnitTest.class);

@Test
public void whenStdExceptionHandling_thenCatchAndLog() {
    try {
        logger.info("Result is " + Integer.parseInt("foobar"));
    } catch (Throwable exception) {
        logger.error("Caught exception:", exception);
    }
}

这段代码会先创建 Logger,然后在 try 块中执行可能抛出异常的操作。当异常发生时,控制台输出如下:

09:29:28.140 [main] ERROR c.b.n.NoExceptionUnitTest 
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

4. 使用 NoException 处理异常

4.1. 默认日志处理器

用 NoException 重写上述代码:

@Test
public void whenDefaultNoException_thenCatchAndLog() {
    Exceptions 
      .log()
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

输出几乎相同:

09:36:04.461 [main] ERROR c.m.n.Exceptions 
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

核心优势:NoException 能用单行代码替代传统的 try/catch 结构,自动处理异常并记录日志。

4.2. 添加自定义 Logger

观察输出会发现,日志记录的类名是 c.m.n.Exceptions 而非我们自己的类。通过传入自定义 Logger 解决:

@Test
public void whenDefaultNoException_thenCatchAndLogWithClassName() {
    Exceptions
      .log(logger)
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

现在日志显示正确的类名:

09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest 
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

4.3. 提供自定义日志消息

默认的 "Caught Exception" 可能不够直观。我们可以通过参数自定义消息:

@Test
public void whenDefaultNoException_thenCatchAndLogWithMessage() {
    Exceptions
      .log(logger, "Something went wrong:")
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

输出变为:

09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest 
  - Something went wrong:
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

4.4. 指定默认值

parseInt() 失败时,我们可能需要返回默认值而非仅记录日志。NoException 支持返回 Optional 结果:

@Test
public void
  givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
    System.out.println("Result is " + Exceptions
      .log(logger, "Something went wrong:")
      .get(() -> Integer.parseInt("foobar"))
      .orElse(-1));
}

控制台同时显示异常日志和默认值:

12:02:26.388 [main] ERROR c.b.n.NoExceptionUnitTest
  - Caught exception java.lang.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...
Result is -1

⚠️ 关键点get() 方法返回 Optional,通过 orElse() 提供回退值。

5. 创建自定义日志处理器

对于更复杂的场景,可以继承 ExceptionHandler 实现自定义逻辑。以下处理器根据异常类型决定是否重新抛出:

public class CustomExceptionHandler extends ExceptionHandler {

Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);

    @Override
    public boolean handle(Throwable throwable) {
        if (throwable.getClass().isAssignableFrom(RuntimeException.class)
          || throwable.getClass().isAssignableFrom(Error.class)) {
            return false;
        } else {
            logger.error("Caught Exception", throwable);
            return true;
        }
    }
}

处理逻辑

  • 返回 false → 重新抛出 RuntimeExceptionError
  • 返回 true → 处理其他异常并记录日志

测试普通异常:

@Test
public void givenCustomHandler_whenError_thenRethrowError() {
    CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
    customExceptionHandler.run(() -> "foo".charAt(5));
}

输出日志:

18:35:26.374 [main] ERROR c.b.n.CustomExceptionHandler 
  - Caught Exception 
j.l.StringIndexOutOfBoundsException: String index out of range: 5
at j.l.String.charAt(String.java:658)
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:20)
...

测试 Error

@Test(expected = Error.class)
public void givenCustomHandler_whenException_thenCatchAndLog() {
    CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
    customExceptionHandler.run(() -> throwError());
}

private static void throwError() {
    throw new Error("This is very bad.");
}

Error 被重新抛出:

Exception in thread "main" java.lang.Error: This is very bad.
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:15)
...

注意:自定义处理器需谨慎设计,避免意外吞没关键异常。

6. 总结

NoException 提供了优雅的异常处理方案:

  • ✅ 单行代码替代冗长的 try/catch
  • ✅ 灵活的日志配置(自定义 Logger/消息)
  • ✅ 支持默认值回退
  • ✅ 可扩展的自定义处理器

在需要简化异常处理逻辑的场景下,NoException 是一个简单粗暴的解决方案,尤其适合需要统一异常处理风格的项目。


原始标题:Introduction to NoException | Baeldung