Java 8: проверенное исключение в предложении forEach [дубликат]

FWIW, у меня была такая же проблема с аналогичной настройкой (Windows XP). Тот же код работал без этой проблемы на Mac (OS X).

Я решил проблему, используя постоянное соединение в классе MySQLi.

См. Здесь для получения дополнительной информации: http://php.net/mysqli.persistconns

Убедитесь, что вы знаете обо всех ошибках / предостережениях, связанных с использованием постоянного соединения.

136
задан Jack 14 November 2016 в 21:15
поделиться

12 ответов

Вы должны поймать исключение до того, как выйдет из лямбда:

s = s.filter(a -> { try { return a.isActive(); } 
                    catch (IOException e) { throw new UncheckedIOException(e); }}});

. Рассмотрите тот факт, что лямбда не оценивается в месте, где вы ее пишете, но при некоторые совершенно несвязанные места, в классе JDK. Таким образом, это будет точка, в которой это исключенное исключение будет выбрано, и в этом месте оно не будет объявлено.

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

public static <T> T uncheckCall(Callable<T> callable) {
  try { return callable.call(); }
  catch (RuntimeException e) { throw e; }
  catch (Exception e) { throw new RuntimeException(e); }
}

Ваш пример будет написан как

return s.filter(a -> uncheckCall(a::isActive))
        .map(Account::getNumber)
        .collect(toSet());

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

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


@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

, и вы можете ожидать получить IOException, брошенное вам в лицо, даже если collect не объявляет об этом. В большинстве случаев , но не во всех случаях , которые вы хотели бы просто перестроить исключение, и обрабатывать его как общий сбой. Во всех этих случаях ничто не теряется в ясности или правильности. Просто остерегайтесь тех других случаев, когда вы действительно хотите реагировать на исключение на месте. Разработчик не узнает компилятором, что есть IOException, чтобы поймать там, и компилятор действительно будет жаловаться, если вы попытаетесь его поймать, потому что мы обманули его, полагая, что такое исключение не может быть выброшено.

177
ответ дан Marko Topolnik 16 August 2018 в 00:31
поделиться
  • 1
    Я видел, как NettyIO делает «подлый бросок». раньше, и я хотел бросить свой стул в окно. & Quot; Что? Где эта проверенная утечка исключений из? & Quot; Это первый законный случай использования для скрытого броска, который я видел еще. Как программист, вы должны проявлять бдительность в отношении того, чтобы показать скрытый бросок. Может быть, лучше всего просто создать другой интерфейс / impl stream, который поддерживает проверенное исключение? – kevinarpe 25 August 2015 в 12:23
  • 2
    Никакой нормальный API не должен доставлять незаблокированные проверенные исключения клиенту, это точно. Внутри API может быть понимание, однако, что проверенное исключение может протекать. Они не причиняют вреда, пока они являются еще одним признаком общего отказа и собираются поймать и обработать оптом. – Marko Topolnik 25 August 2015 в 12:27
  • 3
    @kevinarpe Это точная причина, почему скрытые броски - плохая идея. Короткое окружение компилятора должно смущать будущих сопровождающих. – Thorbjørn Ravn Andersen 24 September 2015 в 15:41
  • 4
    Просто потому, что вам не нравятся правила, это не значит, что это хорошая идея принять закон в свои руки. Ваш совет безответственен, потому что он делает удобство написания кода более важным соображением прозрачности и ремонтопригодности программы. – Brian Goetz 24 October 2015 в 14:56
  • 5
    @brian Просто потому, что что-то правило не означает, что это хорошая идея. Но я удивлен, что вы ссылаетесь на вторую часть моего ответа как на «совет». так как я думал, что я ясно сказал, что предлагаю в качестве решения, и то, что я предлагаю в качестве FYI для заинтересованного читателя, с обильными оговорками. – Marko Topolnik 24 October 2015 в 18:46

Вы также можете распространять свою статическую боль с помощью lambdas, поэтому все это выглядит читаемым:

s.filter(a -> propagate(a::isActive))

propagate здесь принимает java.util.concurrent.Callable в качестве параметра и преобразует любое исключение, пойманное во время вызова в RuntimeException. Существует аналогичный метод преобразования Throwables # распространять (Throwable) в Guava.

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

public class PropagateExceptionsSample {
    // a simplified version of Throwables#propagate
    public static RuntimeException runtime(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException)e;
        }

        return new RuntimeException(e);
    }

    // this is a new one, n/a in public libs
    // Callable just suits as a functional interface in JDK throwing Exception 
    public static <V> V propagate(Callable<V> callable){
        try {
            return callable.call();
        } catch (Exception e) {
            throw runtime(e);
        }
    }

    public static void main(String[] args) {
        class Account{
            String name;    
            Account(String name) { this.name = name;}

            public boolean isActive() throws IOException {
                return name.startsWith("a");
            }
        }


        List<Account> accounts = new ArrayList<>(Arrays.asList(new Account("andrey"), new Account("angela"), new Account("pamela")));

        Stream<Account> s = accounts.stream();

        s
          .filter(a -> propagate(a::isActive))
          .map(a -> a.name)
          .forEach(System.out::println);
    }
}
23
ответ дан Andrey Chaschev 16 August 2018 в 00:31
поделиться

Учитывая эту проблему, я разработал небольшую библиотеку для работы с проверенными исключениями и лямбдами. Пользовательские адаптеры позволяют интегрироваться с существующими функциональными типами:

stream().map(unchecked(URI::new)) //with a static import

https://github.com/TouK/ThrowingFunction/

1
ответ дан Grzegorz Piwowarek 16 August 2018 в 00:31
поделиться

Вы можете свернуть свой собственный вариант Stream, обернув свою лямбда, чтобы выбросить исключенное исключение, а затем позже разблокировать это исключенное исключение в операциях терминала:

@FunctionalInterface
public interface ThrowingPredicate<T, X extends Throwable> {
    public boolean test(T t) throws X;
}

@FunctionalInterface
public interface ThrowingFunction<T, R, X extends Throwable> {
    public R apply(T t) throws X;
}

@FunctionalInterface
public interface ThrowingSupplier<R, X extends Throwable> {
    public R get() throws X;
}

public interface ThrowingStream<T, X extends Throwable> {
    public ThrowingStream<T, X> filter(
            ThrowingPredicate<? super T, ? extends X> predicate);

    public <R> ThrowingStream<T, R> map(
            ThrowingFunction<? super T, ? extends R, ? extends X> mapper);

    public <A, R> R collect(Collector<? super T, A, R> collector) throws X;

    // etc
}

class StreamAdapter<T, X extends Throwable> implements ThrowingStream<T, X> {
    private static class AdapterException extends RuntimeException {
        public AdapterException(Throwable cause) {
            super(cause);
        }
    }

    private final Stream<T> delegate;
    private final Class<X> x;

    StreamAdapter(Stream<T> delegate, Class<X> x) {
        this.delegate = delegate;
        this.x = x;
    }

    private <R> R maskException(ThrowingSupplier<R, X> method) {
        try {
            return method.get();
        } catch (Throwable t) {
            if (x.isInstance(t)) {
                throw new AdapterException(t);
            } else {
                throw t;
            }
        }
    }

    @Override
    public ThrowingStream<T, X> filter(ThrowingPredicate<T, X> predicate) {
        return new StreamAdapter<>(
                delegate.filter(t -> maskException(() -> predicate.test(t))), x);
    }

    @Override
    public <R> ThrowingStream<R, X> map(ThrowingFunction<T, R, X> mapper) {
        return new StreamAdapter<>(
                delegate.map(t -> maskException(() -> mapper.apply(t))), x);
    }

    private <R> R unmaskException(Supplier<R> method) throws X {
        try {
            return method.get();
        } catch (AdapterException e) {
            throw x.cast(e.getCause());
        }
    }

    @Override
    public <A, R> R collect(Collector<T, A, R> collector) throws X {
        return unmaskException(() -> delegate.collect(collector));
    }
}

. Тогда вы можете использовать это же точный способ как Stream:

Stream<Account> s = accounts.values().stream();
ThrowingStream<Account, IOException> ts = new StreamAdapter<>(s, IOException.class);
return ts.filter(Account::isActive).map(Account::getNumber).collect(toSet());

Это решение потребует довольно много шаблонов, поэтому я предлагаю вам взглянуть на библиотеку , которую я уже сделал , что делает именно то, что я описал здесь для всего класса Stream (и более!).

8
ответ дан Jeffrey 16 August 2018 в 00:31
поделиться
  • 1
    Привет ... крошечная ошибка? new StreamBridge<>(ts, IOException.class); - & gt; new StreamBridge<>(s, IOException.class); – kevinarpe 25 August 2015 в 12:41
  • 2
    @kevinarpe Да. Он также должен был сказать StreamAdapter. – Jeffrey 26 August 2015 в 04:03

Если вы не возражаете против использования сторонних библиотек, AOL cyclops-react lib, раскрытие :: Я являюсь автором, имеет класс ExceptionSoftener , который может помочь здесь .

 s.filter(softenPredicate(a->a.isActive()));
0
ответ дан John McClean 16 August 2018 в 00:31
поделиться

Этот хелпер-класс UtilException позволяет использовать любые проверенные исключения в потоках Java, например:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Примечание Class::forName выбрасывает ClassNotFoundException, который отмечен. Сам поток также выбрасывает ClassNotFoundException, а НЕ НЕИСПРАВЛЯЕТ НЕВОЗМОЖНОЕ исключение.

public final class UtilException {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

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

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Многие другие примеры использования (после статического импорта UtilException):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }

Но не используйте его, прежде чем понимать следующие преимущества, недостатки и ограничения:

• Если вызывающий код обрабатывает проверенное исключение, вы ДОЛЖНЫ добавить его в предложение throws метода который содержит поток. Компилятор больше не заставит вас его добавлять, поэтому его легче забыть.

• Если код вызова уже обрабатывает проверенное исключение, компилятор напомнит вам добавить предложение throws к методу декларация, содержащая поток (если вы этого не скажете: исключение никогда не выбрасывается в тело соответствующего оператора try).

• В любом случае вы не сможете окружить сам поток чтобы поймать проверенное исключение ВНУТРИ метода, который содержит поток (если вы попытаетесь, компилятор скажет: «Исключение никогда не выбрасывается в тело соответствующей инструкции try».

• Если вы вызываете метод, который буквально никогда не может генерировать исключение, которое оно объявляет, тогда вы не должны включать предложение throws. Например: new String (byteArr, «UTF-8») выдает UnsupportedEncodingException, но UTF-8 гарантируется спецификацией Java всегда присутствовать. Здесь объявление бросков является неприятным, и любое решение, чтобы заставить его замолчать с минимальным шаблоном, приветствуется.

• Если вы ненавидите проверенные исключения и чувствуете, что их никогда не следует добавлять на язык Java для начала (растущий количество людей думает так, и я НЕ один из них), то просто не добавляйте исключенное исключение в предложение throws метода, содержащего поток. Таким образом, проверенное исключение будет вести себя так же, как исключение UNchecked.

• Если вы реализуете строгий интерфейс, в котором у вас нет возможности добавить объявление бросков, и все же исключение исключение полностью Соответственно, затем обертывание исключения, чтобы получить привилегию бросить его, приводит к stacktrace с ложными исключениями, которые не дают никакой информации о том, что на самом деле пошло не так. Хорошим примером является Runnable.run (), который не бросает никаких проверенных исключений. В этом случае вы можете не добавлять исключенное исключение в предложение throws метода, содержащего поток.

• В любом случае, если вы решили НЕ добавлять (или забывать добавлять), проверенное исключение в предложение throws метода, содержащего поток, должно знать об этих двух последствиях бросания CHECKED исключений:

1) Код вызова не сможет поймать его по имени (если вы пытаетесь, компилятор скажет: «Исключение никогда не выбрасывается в тело соответствующей инструкции try»). Он будет пузыриться и, вероятно, попадет в основной цикл программы с помощью некоторых «исключений catch» или «catch Throwable», которые могут быть тем, что вы хотите в любом случае.

2) Он нарушает принцип наименьшего удивления: он больше не будет достаточно, чтобы поймать RuntimeException, чтобы быть в состоянии гарантировать обнаружение всех возможных исключений. По этой причине я считаю, что это не должно быть сделано в коде кода, а только в бизнес-коде, который вы полностью контролируете.

В заключение: я считаю, что ограничения здесь несерьезны, а класс UtilException могут использоваться без страха. Тем не менее, это зависит от вас!

17
ответ дан MarcG 16 August 2018 в 00:31
поделиться

Расширяя решение @marcg, вы обычно можете бросать и ловить проверенное исключение в потоках; то есть компилятор попросит вас поймать / перебросить, так как вы были вне потоков !!

@FunctionalInterface
public interface Predicate_WithExceptions<T, E extends Exception> {
    boolean test(T t) throws E;
}

/**
 * .filter(rethrowPredicate(t -> t.isActive()))
 */
public static <T, E extends Exception> Predicate<T> rethrowPredicate(Predicate_WithExceptions<T, E> predicate) throws E {
    return t -> {
        try {
            return predicate.test(t);
        } catch (Exception exception) {
            return throwActualException(exception);
        }
    };
}

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwActualException(Exception exception) throws E {
    throw (E) exception;
}

Затем ваш пример будет написан следующим образом (добавление тестов для более четкого отображения):

@Test
public void testPredicate() throws MyTestException {
    List<String> nonEmptyStrings = Stream.of("ciao", "")
            .filter(rethrowPredicate(s -> notEmpty(s)))
            .collect(toList());
    assertEquals(1, nonEmptyStrings.size());
    assertEquals("ciao", nonEmptyStrings.get(0));
}

private class MyTestException extends Exception { }

private boolean notEmpty(String value) throws MyTestException {
    if(value==null) {
        throw new MyTestException();
    }
    return !value.isEmpty();
}

@Test
public void testPredicateRaisingException() throws MyTestException {
    try {
        Stream.of("ciao", null)
                .filter(rethrowPredicate(s -> notEmpty(s)))
                .collect(toList());
        fail();
    } catch (MyTestException e) {
        //OK
    }
}
2
ответ дан PaoloC 16 August 2018 в 00:31
поделиться
  • 1
    Этот пример не компилируется – Roman M 4 July 2016 в 12:03
  • 2
    Привет @RomanM благодарит за это: я исправил недостающий тип возвращаемого значения на методе throwActualException & quot ;. Мы используем это в производстве, поэтому я надеюсь, что он тоже работает на вашей стороне. – PaoloC 6 July 2016 в 08:30

Ваш пример может быть записан как:

import utils.stream.Unthrow;

class Bank{
   ....
   public Set<String> getActiveAccountNumbers() {
       return accounts.values().stream()
           .filter(a -> Unthrow.wrap(() -> a.isActive()))
           .map(a -> Unthrow.wrap(() -> a.getNumber()))
           .collect(Collectors.toSet());
   }
   ....
}

Класс Unthrow можно взять здесь https://github.com/SeregaLBN/StreamUnthrower

1
ответ дан SeregaLBN 16 August 2018 в 00:31
поделиться

Это не отвечает непосредственно на вопрос (есть много других ответов), но пытается избежать проблемы в первую очередь:

По моему опыту необходимость обработки исключений в Stream (или другое лямбда-выражение) часто происходит из-за того, что исключения объявляются из методов, где их не следует бросать. Это часто происходит из-за смешивания бизнес-логики с вводом и выводом. Ваш Account интерфейс является прекрасным примером:

interface Account {
    boolean isActive() throws IOException;
    String getNumber() throws IOException;
}

Вместо того, чтобы бросать IOException на каждый геттер, рассмотрите эту схему:

interface AccountReader {
    Account readAccount(…) throws IOException;
}

interface Account {
    boolean isActive();
    String getNumber();
}

Метод AccountReader.readAccount(…) может читать учетную запись из базы данных или файла или любого другого и вызывать исключение, если это не удается. Он создает объект Account, который уже содержит все значения, готовые к использованию. Поскольку значения уже загружены readAccount(…), геттеры не будут генерировать исключение. Таким образом, вы можете свободно использовать их в лямбдах без необходимости обертывания, маскировки или скрытия исключений.

Конечно, это не всегда возможно сделать так, как я описывал, но часто это происходит, и это приводит к (IMHO):

  • Лучше разделение проблем и после принципа единой ответственности
  • Меньше шаблона: Вам не нужно загромождать свой код с помощью throws IOException для использования, но для удовлетворения компилятора
  • Обработка ошибок: вы обрабатываете ошибки там, где они происходят - при чтении из файла или базы данных, а не где-то в середине вашей бизнес-логики только потому, что вы хотите получить значение поля
  • Возможно, вы сможете сделать Account неизменяемым и извлечь выгоду из его преимуществ (например, безопасность потоков )
  • Вам не нужны «грязные трюки» или обходные пути для использования Account в lambdas (например, в Stream)
3
ответ дан siegi 16 August 2018 в 00:31
поделиться

Использовать метод #propagate (). Пример реализации не-Guava из Java 8 Блог Sam Beran :

public class Throwables {
    public interface ExceptionWrapper<E> {
        E wrap(Exception e);
    }

    public static <T> T propagate(Callable<T> callable) throws RuntimeException {
        return propagate(callable, RuntimeException::new);
    }

    public static <T, E extends Throwable> T propagate(Callable<T> callable, ExceptionWrapper<E> wrapper) throws E {
        try {
            return callable.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw wrapper.wrap(e);
        }
    }
}
5
ответ дан user11153 16 August 2018 в 00:31
поделиться

Он может быть разрешен ниже простым кодом с помощью Stream и Try в AbacusUtil :

Stream.of(accounts).filter(a -> Try.call(a::isActive)).map(a -> Try.call(a::getNumber)).toSet();

Раскрытие информации : Я разработчик AbacusUtil.

3
ответ дан Vivit 16 August 2018 в 00:31
поделиться
3
ответ дан siegi 6 September 2018 в 00:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: