Можно ли объявить, что Поставщик <T> должен выбросить исключение?

Итак, я пытаюсь реорганизовать следующий код:

/**
 * 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, - это превалировать базовую функциональность, например, методы по умолчанию.

Ответ 1

В списке рассылки лямбда это было о чем было сказано. Как вы можете видеть, Брайан Гетц предположил, что альтернативой является написать собственный комбинатор:

Или вы могли бы написать свой собственный тривиальный комбинатор:

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

Вы можете написать его один раз, тем меньше, чтобы время, необходимое для написания оригинальный e-mail. И аналогично один раз для каждого вида SAM, который вы используете.

Я бы предпочел, чтобы мы смотрели на это как на "стеклянное 99% полное", а не на альтернатива. Не все проблемы требуют новых языковых решения. (Не говоря уже о том, что новые языковые функции всегда вызывают новые проблемы.)

В те дни пользовательский интерфейс назывался Block.

Я думаю, что это соответствует JB Nizet answer, предложенному Марко выше.

Позже Брайан объясняет, почему это было спроектировано таким образом (причина проблемы)

Да, вам придется предоставить свои собственные исключительные SAM. Но тогда лямбда конверсия будет работать с ними хорошо.

EG обсудил дополнительную языковую и библиотечную поддержку для этого проблемы, и в конце концов считал, что это была плохая стоимость/польза Компромисс.

Решения на базе библиотеки приводят к взрыву 2x в типах SAM (исключительные против нет), которые плохо взаимодействуют с существующими комбинаторными взрывами для примитивной специализации.

Доступными языковыми решениями были проигравшие из сложность/ценность. Хотя есть альтернатива решения, которые мы будем продолжать изучать, хотя явно не для 8 и, вероятно, не для 9.

Тем временем у вас есть инструменты, чтобы делать то, что вы хотите. я понимаю вы предпочитаете, чтобы мы предоставили вам последнюю милю (и, во-вторых, ваш запрос действительно тонко завуалированный запрос "почему бы вам просто не отказаться от проверенных исключений уже" ), но я думаю, что текущий state позволяет выполнить вашу работу.

Ответ 2

  • Если вы это сделаете, вы не сможете использовать его как Supplier, так как это приведет только к исключению UnsupportedOperationException.

  • Учитывая вышеизложенное, почему бы не создать новый интерфейс и не объявить в нем метод getWithIO?

    @FunctionalInterface
    public interface SupplierWithIO<T> {
        public T getWithIO() throws IOException;
    }
    
  • Возможно, некоторые вещи лучше, чем интерфейсы Java старого стиля? Старая Java-версия не исчезла только потому, что теперь есть Java 8.

Ответ 3

Рассмотрим это общее решение:

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

Ответ 4

Поскольку у меня есть дополнительный момент, чтобы сделать по этому вопросу, я решил добавить свой ответ.

У вас есть выбор написать удобный метод, который:

  • обертывает лямбда с проверенными исключениями, в неконтролируемый,
  • просто вызывает лямбду, исключая исключение.

При первом подходе вам нужен один метод удобства для каждой функциональной сигнатуры метода, тогда как при втором подходе вам нужно всего два метода (кроме примитивных возвращающих методов):

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

Это позволяет вам вставлять вызов одному из этих методов, проходящему в нулевой лямбда, но тот, который закрывает любые аргументы внешней лямбда, которые вы передаете исходному методу. Таким образом вы можете использовать функцию автоматического преобразования лямбда без шаблона.

Например, вы можете написать

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

по сравнению с

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

Я также обнаружил, что перегрузка имени метода упаковки для всех лямбда-сигнатур часто приводит к неоднозначным ошибкам выражения лямбда. Поэтому вам нужны более длинные имена, что приводит к увеличению кода char -for- char, чем при использовании вышеприведенного подхода.

В заключение обратите внимание, что упомянутый подход по-прежнему не исключает возможности написания простых методов оболочки, которые повторно используют uncheckedRun/Call, но я нашел это просто неинтересным, потому что сбережения в лучшем случае незначительны.

Ответ 5

Я добавил свое решение, а не обязательно ответ на свой вопрос, который после некоторых изменений вводит следующее:

@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, как:

  • Мне нужно добавить неконтролируемый вариант каждого метода, который может вызывать IOException.
  • Я предпочитаю очевидное обращение с IOException, вместо того чтобы полагаться на надежду, что вы не забудете поймать UncheckedIOException.