我长期以来一直批评Java中的编译器检查异常机制。 无论您是爱还是恨,都可以肯定一件事:在某些情况下,您不想与他们打交道。 Java中的解决方案是将一个检查过的异常包装在new RuntimeException(e)
但这可以提供较长的堆栈跟踪,而无需添加有用的信息。 有时,我们只想告诉编译器冷静。
事实证明,通过对Java泛型的类型擦除错误功能的某些不当使用,这是有可能的。 看到这一点对于理解Java的内部运作方式具有指导意义。 我们走吧!
这是我们想要的:
public static void main(String[] args) {
businessLogic();
}
private static void businessLogic() {
List<String> configuration = readConfigurationFile();
System.out.println(configuration.get(0));
}
private static List<String> readConfigurationFile() {
try {
return Files.readAllLines(Paths.get("non", "existing", "file"));
} catch (IOException e) {
throw softenException(e);
}
}
注意, businessLogic()
既不捕获IOException
也不声明它throws IOException
。 相反, softenException()
方法将删除异常的检查。 运行它时,我们得到以下堆栈跟踪:
Exception in thread "main" java.nio.file.NoSuchFileException: non\existing\file
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:230)
at java.nio.file.Files.newByteChannel(Files.java:361)
at java.nio.file.Files.newByteChannel(Files.java:407)
at java.nio.file.spi.FileSystemProvider.newInputStream(FileSystemProvider.java:384)
at java.nio.file.Files.newInputStream(Files.java:152)
at java.nio.file.Files.newBufferedReader(Files.java:2784)
at java.nio.file.Files.readAllLines(Files.java:3202)
at java.nio.file.Files.readAllLines(Files.java:3242)
at insanejava.SoftenExceptionsDemo.readConfigurationFile(SoftenExceptionsDemo.java:21)
at insanejava.SoftenExceptionsDemo.businessLogic(SoftenExceptionsDemo.java:15)
at insanejava.SoftenExceptionsDemo.main(SoftenExceptionsDemo.java:11)
! 在main方法中引发的异常是NoSuchFileException
,它是IOException
的子类–已检查的异常! 怎么可能? 为什么程序中的任何方法都不必声明throws IOException
?
这是窍门:
private static RuntimeException softenException(Exception e) {
return checkednessRemover(e);
}
private static <T extends Exception> T checkednessRemover(Exception e) throws T {
throw (T) e;
}
checkednessRemover
方法使用了一个技巧,可以揭示有关Java内部工作的一些信息。 首先,将通用类型参数T绑定到RuntimeException
,以实现softenException
。 这意味着表达式throws T
变成throws RuntimeException
,编译器将其解释为好像没有抛出异常。
但是语句throw (T)e;
理论上应该评估为throw (RuntimeException)e;
。 由于e是NoSuchFileException
,因此您希望此语句导致ClassCastException
。 但是,泛型在Java中的工作方式是,编译器会删除类型信息。 因此,字节码改为throw (Exception)e;
,这很好。
因此,这个奇怪的把戏表明,Java编译器从编译的代码中删除了通用信息,并且检查的异常纯粹是编译器的功能。 没有检查异常的运行时验证。
我会建议在生产代码中使用此技巧吗? 我不知道。 这很奇怪,可能没什么用,但是当我感到邪恶时,我会自己使用它。 如果没有别的,我希望学习可以让您对Java的内部运作有一些见解。
免责声明 :(1)我在其他地方读到了这个技巧,但是我再也找不到源了。 我以为这是Heinz Kabutz的出色Java 时事通讯,但我找不到源。 (2)这在 Lombok项目中 也实现为
@SneakyThrows
。 如果您使用的是Lombok,则在任何情况下都不应重新实现此博客中的技巧。 请改用@SneakyThrows
。