Java 8: Как использовать лямбда-функцию, которая выбрасывает исключение?
Я знаю, как создать ссылку на метод, который имеет параметр типа String
и возвращает int
, это делается следующим образом:
Function<String, Integer>
Однако это не сработает, если функция генерирует исключение. Например, если метод определён так:
Integer myMethod(String s) throws IOException
Как мне определить такую ссылку?
5 ответ(ов)
Вам необходимо сделать одно из следующих действий:
Если это ваш код, то определите собственный функциональный интерфейс, который будет объявлять проверяемое исключение:
@FunctionalInterface public interface CheckedFunction<T, R> { R apply(T t) throws IOException; }
Затем используйте его:
void foo(CheckedFunction f) { ... }
Если это не ваш код, оберните метод
Integer myMethod(String s)
в метод, который не объявляет проверяемое исключение:public Integer myWrappedMethod(String s) { try { return myMethod(s); } catch(IOException e) { throw new UncheckedIOException(e); } }
После этого вы можете сделать так:
Function<String, Integer> f = (String t) -> myWrappedMethod(t);
Или так:
Function<String, Integer> f = (String t) -> { try { return myMethod(t); } catch(IOException e) { throw new UncheckedIOException(e); } };
Это не специфично для Java 8. Вы пытаетесь скомпилировать что-то, эквивалентное следующему коду:
interface I {
void m();
}
class C implements I {
public void m() throws Exception {} // не компилируется
}
В данном случае, метод m()
в классе C
не может объявлять проверяемое исключение (checked exception), поскольку метод m()
в интерфейсе I
не объявляет это исключение. Чтобы код скомпилировался, метод m()
в классе C
должен соответствовать сигнатуре метода в интерфейсе, а это значит, что он не может бросать исключения, которые не указаны в декларации метода интерфейса.
Примечание: Я еще не использовал Java 8, только читал о ней.
Function<String, Integer>
не может выбрасывать IOException
, поэтому вы не можете реализовать код, который бы throws IOException
. Если вы вызываете метод, который ожидает Function<String, Integer>
, то лямбда-выражение, которое вы передаете этому методу, не может выбрасывать IOException
, и точка. Вы можете написать лямбду таким образом (я думаю, что это синтаксис лямбды, точно не уверён):
(String s) -> {
try {
return myMethod(s);
} catch (IOException ex) {
throw new RuntimeException(ex);
// (Или сделать что-то другое с ним...)
}
}
Либо, если метод, которому вы передаете лямбду, написан вами, вы можете определить новый функциональный интерфейс и использовать его в качестве типа параметра вместо Function<String, Integer>
:
public interface FunctionThatThrowsIOException<I, O> {
O apply(I input) throws IOException;
}
Вы можете создать свой собственный FunctionalInterface
, который позволяет выбрасывать исключения, как показано ниже:
@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
void accept(T instance) throws X;
}
После этого вы сможете интегрировать его с использованием лямбда-выражений или ссылок на методы, как показано в следующем примере:
import java.io.FileWriter;
import java.io.IOException;
// лямбда-выражения и паттерн "выполнение вокруг метода" (EAM) для
// управления ресурсами
public class FileWriterEAM {
private final FileWriter writer;
private FileWriterEAM(final String fileName) throws IOException {
writer = new FileWriter(fileName);
}
private void close() throws IOException {
System.out.println("Закрытие вызвано автоматически...");
writer.close();
}
public void writeStuff(final String message) throws IOException {
writer.write(message);
}
//...
public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {
final FileWriterEAM writerEAM = new FileWriterEAM(fileName);
try {
block.accept(writerEAM);
} finally {
writerEAM.close();
}
}
public static void main(final String[] args) throws IOException {
FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("хорошо"));
FileWriterEAM.use("eam2.txt", writerEAM -> {
writerEAM.writeStuff("как");
writerEAM.writeStuff("хорошо");
});
FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);
}
void writeIt() throws IOException {
this.writeStuff("Как ");
this.writeStuff("хорошо ");
this.writeStuff("это");
}
}
Этот код демонстрирует, как удобно управлять ресурсами в Java с использованием лямбда-выражений в сочетании с пользовательским интерфейсом функциональных интерфейсов. При помощи паттерна EAM вы можете быть уверены, что ресурсы будет корректно закрывать, даже если в процессе выполнения возникнет исключение.
Здесь уже опубликовано много отличных ответов. Я просто пытаюсь подойти к решению проблемы с другой точки зрения. Это всего лишь мое мнение, пожалуйста, поправьте меня, если я где-то неправ.
Использование throws в FunctionalInterface — не лучшая идея
Считаю, что навязывать throws IOException в FunctionalInterface — плохая практика по следующим причинам:
Это выглядит как анти-шаблон для Stream и Lambda. Основная идея заключается в том, что вызывающий код сам решает, какой код предоставить и как обрабатывать исключения. Во многих сценариях IOException может быть неуместным для клиента. Например, если клиент получает значение из кеша/памяти, а не выполняет фактический I/O.
Обработка исключений в потоках становится поистине ужасной. Например, вот как будет выглядеть мой код, если я использую ваш API:
acceptMyMethod(s -> { try { Integer i = doSomeOperation(s); return i; } catch (IOException e) { // блок try-catch из-за throws // в функциональном методе, хотя doSomeOperation // может вообще не выбрасывать исключение. e.printStackTrace(); } return null; });
Неприятно, не так ли? Более того, как я уже упоминал, метод doSomeOperation может выбрасывать IOException или нет (в зависимости от реализации клиента/вызывающего), но из-за throws в вашем FunctionalInterface мне всегда приходится писать блок try-catch.
Что делать, если я точно знаю, что этот API выбрасывает IOException
Вероятно, мы путаем FunctionalInterface с типичными интерфейсами. Если вы знаете, что этот API будет выбрасывать IOException, то, скорее всего, вы также знаете о каком-то предопределённом/абстрактном поведении. Я думаю, вам следует определить интерфейс и предоставить вашу библиотеку (с предопределённой/абстрактной реализацией) следующим образом:
public interface MyAmazingAPI { Integer myMethod(String s) throws IOException; }
Но проблема с блоком try-catch все еще остается для клиента. Если я использую ваш API в потоке, мне все равно нужно обрабатывать IOException в ужасном блоке try-catch.
Предоставьте API, удобный для работы с потоками, следующим образом:
public interface MyAmazingAPI { Integer myMethod(String s) throws IOException; default Optional<Integer> myMethod(String s, Consumer<? super Exception> exceptionConsumer) { try { return Optional.ofNullable(this.myMethod(s)); } catch (Exception e) { if (exceptionConsumer != null) { exceptionConsumer.accept(e); } else { e.printStackTrace(); } } return Optional.empty(); } }
Дефолтный метод принимает объект Consumer в качестве аргумента, который будет отвечать за обработку исключения. Теперь с точки зрения клиента код будет выглядеть так:
strStream.map(str -> amazingAPIs.myMethod(str, Exception::printStackTrace)) .filter(Optional::isPresent) .map(Optional::get).collect(toList());
Звучит хорошо, не так ли? Конечно, вместо Exception::printStackTrace можно использовать логгер или другую логику обработки.
Вы также можете предоставить метод, похожий на https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#exceptionally-java.util.function.Function-. Это означает, что вы можете предоставить другой метод, который будет содержать исключение из предыдущего вызова метода. Недостаток в том, что вы делаете свои API состоянием, что означает, что вам нужно учитывать потокобезопасность, а это может привести к снижению производительности. Это просто вариант, который стоит рассмотреть.
Java 8: Преобразование List<V> в Map<K, V>
Вывод типа с помощью рефлексии для лямбд в Java 8
Сортировка ArrayList пользовательских объектов по свойству
Почему не стоит использовать Optional в аргументах в Java 8?
Почему компилятор Java 11 использует invokevirtual для вызова приватных методов?