40

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

/**
 * 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是为了破坏基本功能,例如默认方法。

4

6 回答 6

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

  • 考虑到上述情况,为什么不创建一个新接口并在其中声明getWithIO方法呢?

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

于 2014-03-27T12:46:31.037 回答
21

在 lambda 邮件列表中对此进行了彻底的讨论。正如您所看到的那样,Brian Goetz 建议替代方法是编写您自己的组合器:

或者您可以编写自己的简单组合器:

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

您可以编写一次,花费的时间少于编写原始电子邮件的时间。同样,对于您使用的每种 SAM 一次。

我宁愿我们将其视为“99% 满的玻璃杯”,而不是替代方案。并非所有问题都需要新的语言特性作为解决方案。(更不用说新的语言特性总是会导致新的问题。)

在那些日子里,消费者接口被称为 Block。

我认为这与上面 Marko 建议的JB Nizet 的回答相对应。

后来布赖恩解释了为什么这样设计(问题的原因)

是的,您必须提供自己的特殊 SAM。但随后 lambda 转换将适用于它们。

EG 讨论了对这个问题的额外语言和库支持,最后认为这是一个糟糕的成本/收益权衡。

基于库的解决方案导致 SAM 类型(例外与非例外)的 2 倍爆炸式增长,这与现有的原始专业化组合爆炸式交互非常糟糕。

可用的基于语言的解决方案是复杂性/价值权衡的失败者。尽管我们将继续探索一些替代解决方案——尽管显然不是针对 8,也可能不是针对 9。

与此同时,你拥有做你想做的事的工具。我知道您更喜欢我们为您提供最后一英里(其次,您的请求实际上是一个隐晦的请求,即“您为什么不已经放弃检查的异常”),但我认为当前状态让你完成你的工作。

于 2014-03-27T13:46:11.830 回答
17

编辑

正如多次指出的那样,您不需要任何自定义类,而是使用CallableRunnable

错误,过时的解决方案

考虑这个通用解决方案:

// 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));
于 2017-11-23T15:21:49.760 回答
4

由于我对这个主题还有一点要说明,所以我决定添加我的答案。

您可以选择编写一个方便的方法,它可以:

  1. 将一个检查异常抛出的 lambda 包装成一个未检查的;
  2. 只需调用lambda,取消选中异常。

使用第一种方法,每个功能方法签名需要一个便利方法,而使用第二种方法,您总共需要两种方法(返回原始方法除外):

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; }

这允许您插入对这些方法之一的调用,该方法传入一个空值 lambda,但它关闭了您传递给原始方法的外部 lambda 的任何参数。这样您就可以利用自动 lambda 转换语言功能而无需样板。

例如,你可以写

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

相比

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

我还发现重载所有 lambda 签名的包装方法名称通常会导致模棱两可的 lambda 表达式错误。因此,您需要更长的不同名称,从而导致比上述方法更长的代码字符换字符。

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

于 2014-03-28T07:58:54.093 回答
2

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

@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));
}

我对将 an 转换IOException为 an不太满意UncheckedIOException,因为:

  1. 我需要为每个可以抛出IOException.
  2. 我更喜欢明显的处理IOException,而不是依靠希望你不要忘记抓住UncheckedIOException
于 2014-03-27T13:43:34.430 回答
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;
        }
    }

于 2020-02-14T06:55:02.353 回答