Java 是否可以声明 Supplier<T> 需要抛出异常?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22687943/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 17:17:45  来源:igfitidea点击:

Is it possible to declare that a Supplier<T> needs to throw an Exception?

javajava-8functional-interface

提问by skiwi

So I am trying to refactor the following code:

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

/**
 * 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);
}

I came up with the following to start of with:

我想出了以下开始:

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.");
    }
}

However, it does not compile (obviously), as Suppliercannot throw an IOException. Is there any wayI can add that to the method declaration of getFromConfig?

但是,它不会编译(显然),因为Supplier不能抛出IOException. 有什么办法可以将其添加到 的方法声明中getFromConfig吗?

Or is the only way to do it like the following?

或者是像下面这样做的唯一方法?

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

    public T getWithIO() throws IOException;
}

Update, I just realised that the Supplierinterface is a reallysimple one, as in it has only the get()method. The original reason why I extended Supplieris to preverse the basic functionality, like the default methods for example.

更新,我刚刚意识到该Supplier界面是一个非常简单的界面,因为它只有get()方法。我扩展的最初原因Supplier是为了破坏基本功能,例如默认方法。

回答by Simon Forsberg

  • If you would do that, you wouldn't be able to use it as a Supplieras it would only throw UnsupportedOperationException.

  • Considering the above, why not create a new interface and declare the getWithIOmethod in it?

    @FunctionalInterface
    public interface SupplierWithIO<T> {
        public T getWithIO() throws IOException;
    }
    
  • Perhaps some things are better of as old-style Java interfaces? Old-style Java isn't gone just because there's Java 8 now.

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

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

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

回答by skiwi

I added my own solution, not neccessarilly a direct answer to my question, which introduces the following after some revisions:

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

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

This were all one-time only declarations, now I add two variants of the calling code:

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

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

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

I am not too happy about converting an IOExceptionto an UncheckedIOException, as:

我不太高兴将 an 转换IOException为 an UncheckedIOException,因为:

  1. I would need to add an unchecked variant of every method that can throw an IOException.
  2. I prefer obvious handling of the IOException, instead of relying on hoping that you do not forget to catch the UncheckedIOException.
  1. 我需要为每个可以抛出IOException.
  2. 我更喜欢明显的处理IOException,而不是依赖希望你不要忘记抓住UncheckedIOException

回答by Edwin Dalorzo

In the lambda mailing list this was throughly discussed. As you can see Brian Goetz suggested there that the alternative is to write your own combinator:

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

Or you could write your own trivial combinator:

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

You can write it once, in less that the time it took to write your original e-mail. And similarly once for each kind of SAM you use.

I'd rather we look at this as "glass 99% full" rather than the alternative. Not all problems require new language features as solutions. (Not to mention that new language features always causes new problems.)

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

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

您只需编写一次即可,所用的时间少于编写原始电子邮件所需的时间。并且对于您使用的每种 SAM 类似地进行一次。

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

In those days the Consumer interface was called Block.

在那些日子里,Consumer 接口被称为 Block。

I think this corresponds with JB Nizet's answersuggested by Marko above.

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

Later Brian explains whythis was designed this way (the reason of problem)

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

Yes, you'd have to provide your own exceptional SAMs. But then lambda conversion would work fine with them.

The EG discussed additional language and library support for this problem, and in the end felt that this was a bad cost/benefit tradeoff.

Library-based solutions cause a 2x explosion in SAM types (exceptional vs not), which interact badly with existing combinatorial explosions for primitive specialization.

The available language-based solutions were losers from a complexity/value tradeoff. Though there are some alternative solutions we are going to continue to explore -- though clearly not for 8 and probably not for 9 either.

In the meantime, you have the tools to do what you want. I get that you prefer we provide that last mile for you (and, secondarily, your request is really a thinly-veiled request for "why don't you just give up on checked exceptions already"), but I think the current state lets you get your job done.

是的,您必须提供自己的特殊 SAM。但是随后 lambda 转换可以很好地处理它们。

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

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

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

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

回答by Marko Topolnik

Since I have an additional point to make on this subject, I have decided to add my answer.

由于我对这个主题有额外的观点,我决定添加我的答案。

You have the choice to write a convenience method which either:

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

  1. wraps a checked-exception-throwing lambda into an unchecked one;
  2. simply callsthe lambda, unchecking the exception.
  1. 将一个已检查的异常抛出 lambda 包装成一个未检查的;
  2. 只需调用lambda,取消检查异常。

With the first approach, you need one convenience method per functional method signature, whereas with the second approach, you need a total of two methods (except for primitive-returning methods):

对于第一种方法,每个函数方法签名需要一个方便的方法,而对于第二种方法,您总共需要两个方法(原始返回方法除外):

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

This allows you to insert a call to one of these methods passing in a nullary lambda, but one which is closing overany arguments of the outside lambda, which you are passing to your original method. This way you get to leverage the automatic lambda conversionlanguage feature without boilerplate.

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

For example, you can write

例如,你可以写

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

compared to

相比

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

I have also found that overloading the wrapping method name for all lambda signatures often leads to ambiguous lambda expressionerrors. So you need longer distinct names, resulting in ever longer code char-for-char than with the above approach.

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

In the end, note that the said approach still doesn't preclude writing simple wrapper methods which reuse uncheckedRun/Call, but I found it simply uninteresting because the savings are negligible at best.

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

回答by Vladimir

Edit

编辑

As pointed many times, you don't need any custom class, use Callableand Runnableinstead

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

Wrong, outdated solution

错误的、过时的解决方案

Consider this generic solution:

// 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));

考虑这个通用解决方案:

// 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));

回答by Deepanshu mishra

We can use this generic solution also. Any type of exception we handle by this approach.

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

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