是否可以声明供应商<t>需要抛出异常?</t>

时间:2014-03-27 12:40:27

标签: java java-8 functional-interface

所以我试图重构以下代码:

/**
 * Returns the duration from the config file.
 * 
 * @return  The duration.
 */
private Duration durationFromConfig() {
    try {
        return durationFromConfigInner();
    } catch (IOException ex) {
        throw new IllegalStateException("The config file (\"" + configFile + "\") has not been found.");
    }
}

/**
 * Returns the duration from the config file.
 * 
 * Searches the log file for the first line indicating the config entry for this instance.
 * 
 * @return  The duration.
 * @throws FileNotFoundException If the config file has not been found.
 */
private Duration durationFromConfigInner() throws IOException {
    String entryKey = subClass.getSimpleName();
    configLastModified = Files.getLastModifiedTime(configFile);
    String entryValue = ConfigFileUtils.readFileEntry(configFile, entryKey);
    return Duration.of(entryValue);
}

我想出了以下内容:

private <T> T getFromConfig(final Supplier<T> supplier) {
    try {
        return supplier.get();
    } catch (IOException ex) {
        throw new IllegalStateException("The config file (\"" + configFile + "\") has not been found.");
    }
}

然而,由于Supplier无法抛出IOException,因此无法编译(显然)。有以任何方式我可以将其添加到getFromConfig的方法声明中吗?

或者是唯一的方法,如下所示?

@FunctionalInterface
public interface SupplierWithIO<T> extends Supplier<T> {
    @Override
    @Deprecated
    default public T get() {
        throw new UnsupportedOperationException();
    }

    public T getWithIO() throws IOException;
}

更新,我刚刚意识到Supplier界面是真正简单界面,因为它只有get()方法。我扩展Supplier的原因是为了预先扩展基本功能,例如默认方法。

6 个答案:

答案 0 :(得分:14)

在lambda邮件列表中,这是throughly discussed。正如你所看到的,Brian Goetz建议选择编写自己的组合器:

  

或者你可以编写自己的琐碎组合器:

static<T> Block<T> exceptionWrappingBlock(Block<T> b) {
     return e -> {
         try { b.accept(e); }
         catch (Exception e) { throw new RTE(e); }
     };
}
     

你可以写一次,少花点时间来写你的   原始电子邮件。对于您使用的每种SAM都类似一次。

     

我宁愿把这看作是&#34;玻璃满99%&#34;而不是   替代。并非所有问题都需要新的语言功能   解决方案。 (更不用说新的语言功能总是会导致   新问题。)

在那些日子里,Consumer界面被称为Block。

我认为这与上面Marko建议的JB Nizet's answer相对应。

后来Brian explains why这是以这种方式设计的(问题的原因)

  

是的,您必须提供自己的特殊SAM。但是后来lambda   转换对他们来说很好。

     

EG讨论了额外的语言和图书馆支持   问题,最后觉得这是一个不好的成本/收益   折衷。

     

基于库的解决方案导致SAM类型爆发2倍(特殊情况   vs not),它与现有的组合爆炸相互作用很严重   原始专业化。

     

基于语言的可用解决方案是a的输家   复杂性/价值权衡。虽然有一些选择   我们将继续探索的解决方案 - 尽管显然不是   8,也许不是9。

     

与此同时,你有工具可以做你想做的事。我明白了   你更喜欢我们为你提供最后一英里(其次,你的   请求真的是一个薄薄的请求&#34;为什么不是你   已放弃已检查的例外&#34;),但我认为目前   国家允许你完成你的工作。

答案 1 :(得分:9)

  • 如果您这样做,您将无法将其用作Supplier,因为它只会抛出UnsupportedOperationException。

  • 考虑到上述情况,为什么不创建新界面并在其中声明getWithIO方法?

    @FunctionalInterface
    public interface SupplierWithIO<T> {
        public T getWithIO() throws IOException;
    }
    
  • 也许有些东西比旧式Java界面更好?旧式Java并没有因为现在的Java 8而消失。

答案 2 :(得分:6)

考虑这个通用解决方案:

// We need to describe supplier which can throw exceptions
@FunctionalInterface
public interface ThrowingSupplier<T> {
    T get() throws Exception;
}

// Now, wrapper
private <T> T callMethod(ThrowingSupplier<T> supplier) {
    try {
        return supplier.get();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
        return null;
}

// And usage example
String methodThrowsException(String a, String b, String c) throws Exception {
    // do something
}

String result = callMethod(() -> methodThrowsException(x, y, z));

答案 3 :(得分:1)

我添加了自己的解决方案,而不是直接回答我的问题,在一些修订后引入了以下内容:

@FunctionalInterface
public interface CheckedSupplier<T, E extends Exception> {
    public T get() throws E;
}

private <R> R getFromConfig(final String entryKey, final Function<String, R> converter) throws IOException {
    Objects.requireNonNull(entryKey);
    Objects.requireNonNull(converter);
    configLastModified = Files.getLastModifiedTime(configFile);
    return ConfigFileUtils.readFileEntry(configFile, entryKey, converter);
}

private <T> T handleIOException(final CheckedSupplier<T, IOException> supplier) {
    Objects.requireNonNull(supplier);
    try {
        return supplier.get();
    } catch (IOException ex) {
        throw new IllegalStateException("The config file (\"" + configFile + "\") has not been found.");
    }
}

这是所有一次性声明,现在我添加了两个调用代码变体:

private Duration durationFromConfig() {
    return handleIOException(() -> getFromConfig(subClass.getSimpleName(), Duration::of));
}

private int threadsFromConfig() {
    return handleIOException(() -> getFromConfig(subClass.getSimpleName() + "Threads", Integer::parseInt));
}

我对将IOException转换为UncheckedIOException感到不满意,因为:

  1. 我需要添加一个可以抛出IOException的每个方法的未经检查的变体。
  2. 我更喜欢明显处理IOException,而不是依赖于希望你不要忘记捕捉UncheckedIOException

答案 4 :(得分:1)

由于我在这个问题上还有一点要点,我决定加上我的答案。

您可以选择编写便捷方法:

  1. 将一个检查异常的lambda包装成一个未经检查的lambda;
  2. 只需调用 lambda,取消选中该异常。
  3. 使用第一种方法,每个功能方法签名需要一个便捷方法,而使用第二种方法,您需要总共两种方法(原始返回方法除外):

    static <T> T uncheckCall(Callable<T> callable) {
      try { return callable.call(); }
      catch (Exception e) { return sneakyThrow(e); }
    }
     static void uncheckRun(RunnableExc r) {
      try { r.run(); } catch (Exception e) { sneakyThrow(e); }
    }
    interface RunnableExc { void run() throws Exception; }
    

    这允许您插入对其中一个方法的调用,这些方法传入一个nullary lambda,但是关闭外部lambda的任何参数,您将传递给原始方法。这样您就可以在没有样板的情况下利用自动lambda转换语言功能。

    例如,你可以写

    stream.forEachOrdered(o -> uncheckRun(() -> out.write(o)));
    

    相比
    stream.forEachOrdered(uncheckWrapOneArg(o -> out.write(o)));
    

    我还发现,重载所有lambda签名的包装方法名称通常会导致不明确的lambda表达式错误。因此,您需要更长的不同名称,从而导致char-for-char的代码比上述方法更长。

    最后,请注意,所述方法仍然不能排除编写重用uncheckedRun/Call的简单包装方法,但我发现它简直无趣,因为节省的费用最多可以忽略不计。

答案 5 :(得分:0)

我们也可以使用这种通用解决方案。我们通过这种方法处理的任何类型的异常。

    @FunctionalInterface
    public interface CheckedCall<T, E extends Throwable> {
        T call() throws E;
    }

    public <T> T logTime(CheckedCall<T, Exception> block) throws Exception {

        Stopwatch timer = Stopwatch.createStarted();
        try {
            T result = block.call();
            System.out.println(timer.stop().elapsed(TimeUnit.MILLISECONDS));
            return result;
        } catch (Exception e) {
            throw e;
        }
    }