异常作弊– Java 8 Lambdas
撇开关于Checked vs Runtime异常的宗教辩论,有时由于库的构造不佳,处理Checked示例会使您发疯。
考虑一下您可能要编写的以下代码片段:
public void createTempFileForKey(String key) {Map<String, File> tempFiles = new ConcurrentHashMap<>();//does not compile because it throws an IOException!!tempFiles.computeIfAbsent(key, k -> File.createTempFile(key, ".tmp"));
}
为了使其编译,您需要捕获使您留下此代码的异常:
public void createTempFileForKey(String key) {Map<String, File> tempFiles = new ConcurrentHashMap<>();tempFiles.computeIfAbsent(key, k -> {try {return File.createTempFile(key, ".tmp");}catch(IOException e) {e.printStackTrace();return null;}});
}
尽管可以编译,但是IOException
已经有效地被吞没了。 应该通知此方法的用户已引发异常。
为了解决这个问题,您可以将IOException包装在通用的RuntimeException中,如下所示:
public void createTempFileForKey(String key) throws RuntimeException {Map<String, File> tempFiles = new ConcurrentHashMap<>();tempFiles.computeIfAbsent(key, k -> {try {return File.createTempFile(key, ".tmp");}catch(IOException e) {throw new RuntimeException(e);}});
}
这段代码确实抛出了一个Exception,但是没有抛出打算由该代码抛出的实际IOException。 那些只支持RuntimeExceptions的人可能会对这段代码感到满意,特别是如果可以改进解决方案以创建自定义的IORuntimeException的话。 尽管如此,大多数人还是以这种方式编写代码,他们希望他们的方法能够从File.createTempFile
方法中抛出经过检查的IOException
。
这样做的自然方法有些复杂,看起来像这样:
public void createTempFileForKey(String key) throws IOException{Map<String, File> tempFiles = new ConcurrentHashMap<>();try {tempFiles.computeIfAbsent(key, k -> {try {return File.createTempFile(key, ".tmp");} catch (IOException e) {throw new RuntimeException(e);}});}catch(RuntimeException e){if(e.getCause() instanceof IOException){throw (IOException)e.getCause();}}
}
从lambda内部,您必须捕获IOException,将其包装在RuntimeException中并抛出该RuntimeException。 Lambda必须捕获RuntimeException的包装并重新抛出IOException。 确实非常丑陋!
在理想的世界中,我们需要做的就是从lambda内抛出已检查的异常,而不必更改computeIfAbsent的声明。 换句话说,抛出检查异常,就好像它是运行时异常一样。 但是不幸的是,Java不允许我们这样做……
除非我们作弊,否则那不是! 这里有两种方法可以精确地执行我们想要的操作,即抛出检查异常,就好像它是运行时异常一样。
方法1 –使用泛型:
public static void main(String[] args){doThrow(new IOException());}static void doThrow(Exception e) {CheckedException.<RuntimeException> doThrow0(e);}static <E extends Exception>void doThrow0(Exception e) throws E {throw (E) e;}
请注意,我们已经创建并抛出了IOException,而没有在main方法中声明它。
方法2 –使用不安全:
public static void main(String[] args){getUnsafe().throwException(new IOException());}private static Unsafe getUnsafe(){try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);return (Unsafe) theUnsafe.get(null);} catch (Exception e) {throw new AssertionError(e);}}
再次,我们设法抛出IOException而不在方法中声明它。
无论您喜欢哪种方法,我们现在都可以通过这种方式自由编写原始代码:
public void createTempFileForKey(String key) throws IOException{Map<String, File> tempFiles = new ConcurrentHashMap<>();tempFiles.computeIfAbsent(key, k -> {try {return File.createTempFile(key, ".tmp");} catch (IOException e) {throw doThrow(e);}});}private RuntimeException doThrow(Exception e){getUnsafe().throwException(e);return new RuntimeException();}private static Unsafe getUnsafe(){try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);return (Unsafe) theUnsafe.get(null);} catch (Exception e) {throw new AssertionError(e);}}
doThrow()
方法显然将封装在某些实用程序类中,从而使您的代码在createTempFileForKey()
非常干净。
翻译自: https://www.javacodegeeks.com/2015/05/cheating-with-exceptions-java-8-lambdas.html