英文:
Application of @Sneaky Throws in lombok
问题
我在Java中使用Lombok库,并发现了一个叫做@SneakyThrows的注解。
正如文档所述:
> @SneakyThrows欺骗了编译器。换句话说,Lombok不会包装或替换抛出的受检异常,而是让编译器认为它是一个未经检查的异常。
换句话说,这是一种在编译时绕过异常的方法。但在我看来,这不应该是处理异常的正确方式,因为绕过的异常可能会在运行时显示出奇怪的行为。
那么,在什么情况下应该使用@SneakyThrows呢?
英文:
I was playing with the Lombok library in Java and found an annotation called @SneakyThrows.
As the documentation states:
> @SneakyThrows fakes out the compiler. In other words, Lombok doesn't wrap or replace the thrown checked exception, but makes the compiler think that it is an unchecked exception.
With other words, this is a way to bypass exceptions at compile time. But in my opinion this should not be the correct way of handling exceptions, because the bypassed exception can show weird behaviour at runtime.
So in which scenario should @SneakyThrows be used?
答案1
得分: 35
添加到现有答案中。我个人不喜欢受检异常。更多信息请参阅:https://phauer.com/2015/checked-exceptions-are-evil/
雪上加霜的是,在避免受检异常时,代码会变得臃肿。考虑使用@SneakyThrows:
List<Instant> instantsSneaky = List.of("2020-09-28T12:30:08.797481Z")
.stream()
.map(Example::parseSneaky)
.collect(Collectors.toList());
@SneakyThrows
private static Instant parseSneaky(String queryValue) {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(queryValue).toInstant();
}
与非@SneakyThrows版本相比:
private static Instant parseNonSneaky(String queryValue) throws ParseException {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(queryValue).toInstant();
}
List<Instant> instantsNonSneaky = List.of("2020-09-28T12:30:08.797481Z")
.stream()
.map(timeStamp -> {
try {
return parseNonSneaky(timeStamp);
} catch (ParseException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
因此,使用@SneakyThrows能够使代码更加清晰。
英文:
To add to the existing answers. I personally dislike checked exceptions. See for more info: https://phauer.com/2015/checked-exceptions-are-evil/
To add insult to injury, the code gets bloated when avoiding the checked exceptions. Consider the usage of @SneakyThrows:
List<Instant> instantsSneaky = List.of("2020-09-28T12:30:08.797481Z")
.stream()
.map(Example::parseSneaky)
.collect(Collectors.toList());
@SneakyThrows
private static Instant parseSneaky(String queryValue) {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(queryValue).toInstant();
}
versus non-@SneakyThrows
private static Instant parseNonSneaky(String queryValue) throws ParseException {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(queryValue).toInstant();
}
List<Instant> instantsNonSneaky = List.of("2020-09-28T12:30:08.797481Z")
.stream()
.map(timeStamp -> {
try {
return parseNonSneaky(timeStamp);
} catch (ParseException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
Hence the applicance of @SneakyThrows enables much cleaner code.
答案2
得分: 11
我相信这里的意图是让编译器不要求在方法声明中添加 throws 语句来抛出任何异常。
例如,如果方法是这样的:
public void throwsCheckedException() {
throw new IOException("IO exception thrown");
}
这会导致编译时异常,需要修改为:
public void throwsCheckedException() throws IOException {
throw new IOException("IO exception thrown");
}
注解 @SneakThrows
可以缓解这个问题 - 原始方法声明如下:
@SneakyThrows
public void throwsCheckedException() {
throw new IOException("IO exception thrown");
}
这不会引起编译时错误。注意,集成开发环境(IDE)可能仍会将其标记为错误,例如在 IntelliJ 中,您需要使用 Lombok 插件。
英文:
I believe the intention here is to cause the compiler to not require a throws whatever Exception to be added to the method declaration.
For example if the method was
public void throwsCheckedException() {
throw new IOException("IO exception thrown");
}
This would cause a compile time exception requiring
public void throwsCheckedException() throws IOException {
throw new IOException("IO exception thrown");
}
The annotation @SneakThrows mitigates this - original method declared as
@SneakyThrows
public void throwsCheckedException() {
throw new IOException("IO exception thrown");
}
This will not cause a compile time error.
Note IDEs might still highlight this as an error, for example in IntelliJ you will need to utilise the Lombok plugin.
答案3
得分: 3
我认为文档在这个问题上非常清楚:
> 当您想要退出已检查异常机制时的常见用例集中在以下两种情况周围:
>
>
> 1. 需要过于严格的接口,例如 Runnable - 无论哪个异常从您的 run() 方法传播出来,无论是否经过检查,它都将被传递给线程的未处理异常处理程序。捕获已检查的异常并将其包装在某种 RuntimeException 中只会掩盖实际问题的根本原因。
>
>
>
>
> 2. 一个“不可能”的异常。例如,new String(someByteArray, "UTF-8"); 声明可以抛出 UnsupportedEncodingException,但根据 JVM 规范,UTF-8 必须始终可用。在这种情况下,出现 UnsupportedEncodingException 的可能性与在使用 String 对象时出现 ClassNotFoundError 一样低,而您也不会捕获这些异常!
英文:
I think the documentation is very clear on this:
> Common use cases for when you want to opt out of the checked exception
> mechanism center around 2 situations:
>
>
> 1. A needlessly strict interface, such as Runnable - whatever exception propagates out of your run() method, checked or not, it will
> be passed to the Thread's unhandled exception handler. Catching a
> checked exception and wrapping it in some sort of RuntimeException is
> only obscuring the real cause of the issue.
>
>
>
>
> 1. An 'impossible' exception. For example, new String(someByteArray, "UTF-8"); declares that it can throw an UnsupportedEncodingException
> but according to the JVM specification, UTF-8 must always be
> available. An UnsupportedEncodingException here is about as likely as
> a ClassNotFoundError when you use a String object, and you don't catch
> those either!
答案4
得分: 1
在JAVA 8及以上版本中,特别是在使用lambda时,使用并不容易。
- 主要考虑用于旧版本的Java 8。
- 它本身的目的是故意抛出异常,例如用于警告。通过这种方式,其他服务/程序/代码可以确定应该如何处理请求/响应流程。如果您已经有了现有的机制,就不需要担心它。
@SneakyThrows
在当前传统应用程序开发中用途不大,可能在某些状态机程序中会有必要使用(尽管我在这方面没有专业知识),以确定程序当前流程的状态。这只是不同场景中的一个示例,可能还有其他情况。
英文:
In the JAVA 8 and above when using lambda especially its not an easy way to use.
- Consider it mostly for older versions of Java 8.
- The purpose itself is to throw an exception deliberately for example for warning. By this the other services/program/code can identify how the request/response flow should be handled. If you already have mechanism in place no need to worry about it.
@SneakyThrows
is not of much use in current traditional application development, could be used in some kinda state machine programs where it would be necessary (i do not have expertise in it though) to determine the state of the programs current flow. This is just 1 example of different scenarios there maybe more.
答案5
得分: 1
通常情况下,当我们拥有一个接口,并且在接口中声明的方法有多个实现时,处理这些实现中抛出的异常的签名变化可能会变得困难。
假设我们有一个接口和两个实现。
public interface A {
void methodSignature(String x); // 由于实现中抛出的各种异常,必须进行更改
}
public class B implements A {
@Override
void methodSignature(String x) throws IOException {
// B的实现
}
}
public class C implements A {
@Override
void methodSignature(String x) throws ApiException {
// C的实现
}
}
我们可以在IOException和ApiException的位置使用通用的throws Exception,但如果有更多类型的异常需要处理,可能不够清晰。
因此,我们使用@Sneakythrows,并将异常发送到使用@ControllerAdvice注解的GlobalException处理程序,这将控制流程。
public interface A {
void methodSignature(String x);
}
public class B implements A {
@Override
@SneakyThrows
void methodSignature(String x) {
// B的实现
}
}
public class C implements A {
@Override
@SneakyThrows
void methodSignature(String x) {
// C的实现
}
}
这在特定情况下有助于我们与接口使用者达成的契约。
全局异常处理程序的示例如下:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(
value = {HttpMessageNotReadableException.class, DataAccessException.class, Exception.class, RuntimeException.class})
public ResponseEntity defaultWebExceptionHandler(Exception ex, HttpServletRequest servletRequest, HttpServletResponse httpResponse) {
LOGGER.error("A controller threw an exception", ex);
ResponseEntity returnValue = null;
ErrorInfo errorInfo = new ErrorInfo();
try {
throw ex;
} catch (ResourceNotFoundException resourceNotFound) {
errorInfo.setUserMessage(resourceNotFound.getMessage());
errorInfo.setTechnicalDetails(resourceNotFound.getMessage());
errorInfo.setMessageCode(HttpStatus.NOT_FOUND.toString());
returnValue = new ResponseEntity(errorInfo, HttpStatus.NOT_FOUND);
} catch (HttpMessageNotReadableException hme) {
// ...
} catch (DataAccessException dae) {
// ...
} catch (Exception exception) {
// ...
}
configureHttpResponse(httpResponse, null, servletRequest.getRequestURI());
return returnValue;
}
}
以上。
英文:
Normally when we have an interface and there are more than one implementations of the methods declared in the interface we might find it difficult to handle the signature changes for the exceptions thrown in the implementations.
Say we have an interface and 2 implementations.
public interface A {
void methodSignature(String x); // This has to be changed owing to the various Exception thrown from the implementations
}
public class B implements A {
@Override
void methodSignature(String x) throws IOException {
// implementation for B
}
}
public class C implements A {
@Override
void methodSignature(String x) throws ApiException {
// implementation of C
}
}
We can use a common throws Exception in place of IOException and ApiException but if there are more types of exceptions to be handled it might not be clear.
Hence we use @Sneakythrows and send the exceptions to GlobalException handlers annotated with @ControllerAdvice that would control the flow.
public interface A {
void methodSignature(String x);
}
public class B implements A {
@Override
@SneakyThrows
void methodSignature(String x) {
// implementation for B
}
}
public class C implements A {
@Override
@SneakyThrows
void methodSignature(String x) {
// implementation of C
}
}
This in particular helps when we have a contract with the users of the interfaces finalised.
A sample of global exception handler looks like below
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(
value = {HttpMessageNotReadableException.class, DataAccessException.class, Exception.class, RuntimeException.class})
public ResponseEntity defaultWebExceptionHandler(Exception ex, HttpServletRequest servletRequest, HttpServletResponse httpResponse) {
LOGGER.error("A controller threw an exception", ex);
ResponseEntity returnValue = null;
ErrorInfo errorInfo = new ErrorInfo();
try {
throw ex;
} catch (ResourceNotFoundException resourceNotFound) {
errorInfo.setUserMessage(resourceNotFound.getMessage());
errorInfo.setTechnicalDetails(resourceNotFound.getMessage());
errorInfo.setMessageCode(HttpStatus.NOT_FOUND.toString());
returnValue = new ResponseEntity(errorInfo, HttpStatus.NOT_FOUND);
} catch (HttpMessageNotReadableException hme) {
// ...
} catch (DataAccessException dae) {
// ...
} catch (Exception exception) {
// ...
}
configureHttpResponse(httpResponse, null, servletRequest.getRequestURI());
return returnValue;
}
}
答案6
得分: 0
我经常在以下情况下使用@SneakyThrows
注解:
- 当使用lambda表达式时,抑制try-catch样板代码
- 当特定异常应终止整个进程且无需强制要求层次结构的任何调用者显式处理它时(虽然也可以在这里使用RuntimeException)
- 使用第三方API强制您处理异常,但您无需这样做(与2.相呼应)
英文:
I regularly use the @SneakyThrows
annotation in the following scenarios:
- Supress try-catch boilerplate when using lambdas
- When a specific exception should terminate the entire process and there is no need to force any caller of the hierarchy to handle it explicitly (although RuntimeException can be used here as well)
- Usage of third-party APIs forcing you to handle an exception but you have no need to (goes hand in hand with 2.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论