欺骗异常 – Java 8 Lambdas

抛开检查异常和运行时异常的宗教争论,有很多次那些处理检查异常的例子的可怜构造类库就能让你发疯。

考虑下面你可能想要写的一小段代码:

public void createTempFileForKey(String key) {
  Map<String, File> tempFiles = new ConcurrentHashMap<>();
  //不编译,因为抛出了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);
        }
    });
}

这段代码抛出了一个异常,但不是这段代码应该自然抛出的那个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();
            }
        }
}

从lamabda内部,你必须捕获异常,把它包装成一个RuntimeException并抛出这个RuntimeException.lambda必须捕获这个RuntimeException解包并重新抛出这个IOException.所有这些的确非常丑陋。

理想的做法是在lamabda内部抛出一个检查异常,不用改变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;
    }

注意我们创建并抛出了一个没有在main方法里声明的IOException。

方法2—用Unsafe类:

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()方法中的代码更加简洁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值