Потоки Java 8 и попробуйте с ресурсами
Я думал, что stream API был здесь, чтобы сделать код более удобным для чтения.
Я нашел что-то очень раздражающее. Интерфейс Stream
(java.util.stream.Stream
) расширяет интерфейс AutoClosable
(java.lang.AutoCloseable
)
Итак, если вы хотите правильно закрыть свои потоки, вы должны использовать try с ресурсами.
Листинг 1. Не очень приятно, потоки не закрыты.
public void noTryWithResource() {
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
@SuppressWarnings("resource") List<ImageView> collect = photos.stream()
.map(photo -> new ImageView(new Image(String.valueOf(photo)))).collect(Collectors.<ImageView>toList());
}
Листинг 2. С помощью 2 исправленных попыток: (
public void tryWithResource() {
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
try (Stream<Integer> stream = photos.stream()) {
try (Stream<ImageView> map = stream
.map(photo -> new ImageView(new Image(String.valueOf(photo))))) {
List<ImageView> collect = map.collect(Collectors.<ImageView>toList());
}
}
}
Листинг 3. Поскольку map
возвращает поток, функции stream()
и map()
должны быть закрыты.
public void tryWithResource2() {
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
try (Stream<Integer> stream = photos.stream();
Stream<ImageView> map = stream
.map(photo -> new ImageView(new Image(String.valueOf(photo))))) {
List<ImageView> collect = map.collect(Collectors.<ImageView>toList());
}
}
Пример, который я даю, не имеет никакого смысла. Я заменил Path
на jpg изображения с помощью Integer
, для примера. Но не позволяйте вам отвлекаться на эти детали.
Каков наилучший способ обойти эти автоматически закрывающиеся потоки.
Я должен сказать, что меня не устраивает ни один из трех вариантов, которые я показал.
Как вы думаете? Существуют ли еще более элегантные решения?
Ответы
Ответ 1
Вы используете @SuppressWarnings("resource")
, который предположительно подавляет предупреждение о незакрытом ресурсе. Это не одно из предупреждений, исходящих от javac
. Веб-поиск, похоже, указывает на то, что Eclipse выдает предупреждения, если AutoCloseable
остается незамкнутым.
Это разумное предупреждение в соответствии с спецификацией Java 7, в которой представлен AutoCloseable
:
Ресурс, который должен быть закрыт, когда он больше не нужен.
Однако спецификация Java 8 для AutoCloseable
была ослаблена, чтобы удалить предложение "должно быть закрыто". Теперь он говорит, в частности,
Объект, который может содержать ресурсы... пока он не будет закрыт.
Возможно, и на самом деле общий, для базового класса реализовать AutoCloseable, даже если не все его подклассы или экземпляры будут содержать доступные ресурсы. Для кода, который должен работать в полной общности или когда известно, что экземпляр AutoCloseable требует освобождения ресурса, рекомендуется использовать конструкции try-with-resources. Однако при использовании таких средств, как Stream, которые поддерживают как формы на основе ввода-вывода, так и не-ввода-вывода, блоки try-with-resources вообще не нужны при использовании форм, отличных от I/O.
Этот вопрос широко обсуждался в группе экспертов лямбды; это сообщение обобщает решение. Среди прочего он упоминает изменения спецификации AutoCloseable
(цитируется выше) и спецификации BaseStream
(цитируются другими ответами). В нем также упоминается возможная необходимость корректировать инспектор кода Eclipse для измененной семантики, по-видимому, безоговорочно исключать предупреждения для объектов AutoCloseable
. По-видимому, это сообщение не дошло до людей Eclipse или они еще не изменили его.
В общем, если предупреждения Eclipse побуждают вас думать, что вам нужно закрыть все теги AutoCloseable
, что неверно. Необходимо закрыть только определенные объекты AutoCloseable
. Eclipse должен быть исправлен (если он еще не был), чтобы не выдавать предупреждения для всех объектов AutoCloseable
.
Ответ 2
Вам нужно только закрыть потоки, если поток должен выполнить любую очистку, как правило, ввода-вывода. В вашем примере используется HashSet, поэтому его не нужно закрывать.
из Stream javadoc:
Как правило, только потоки, источник которых является каналом ввода-вывода (например, те, которые возвращаются файлами Files.lines(Path, Charset)), требуют закрытия. Большинство потоков поддерживаются коллекциями, массивами или генерирующими функциями, которые не требуют специального управления ресурсами.
Итак, в вашем примере это должно работать без проблем
List<ImageView> collect = photos.stream()
.map(photo -> ...)
.collect(toList());
ИЗМЕНИТЬ
Даже если вам нужно очистить ресурсы, вы сможете использовать только один try-with-resource. Предположим, что вы читаете файл, где каждая строка в файле - это путь к изображению:
try(Stream<String> lines = Files.lines(file)){
List<ImageView> collect = lines
.map(line -> new ImageView( ImageIO.read(new File(line)))
.collect(toList());
}
Ответ 3
"Closeable" означает "может быть закрыт", а не "должен быть закрыт".
Это было в прошлом, например. см. ByteArrayOutputStream
:
Закрытие a ByteArrayOutputStream
не имеет эффекта.
И теперь это верно для Stream
, где документация
вы также можете добавить вторичный Stream
к управлению ресурсами без дополнительного try
:
final Path p = Paths.get(System.getProperty("java.home"), "COPYRIGHT");
try(Stream<String> stream=Files.lines(p, StandardCharsets.ISO_8859_1);
Stream<String> filtered=stream.filter(s->s.contains("Oracle"))) {
System.out.println(filtered.count());
}
Ответ 4
Можно создать метод утилиты, который надежно закрывает потоки с помощью инструкции try-with-resource.
Это немного похоже на try-finally, который является выражением (что-то, что имеет место, например, Scala).
/**
* Applies a function to a resource and closes it afterwards.
* @param sup Supplier of the resource that should be closed
* @param op operation that should be performed on the resource before it is closed
* @return The result of calling op.apply on the resource
*/
private static <A extends AutoCloseable, B> B applyAndClose(Callable<A> sup, Function<A, B> op) {
try (A res = sup.call()) {
return op.apply(res);
} catch (RuntimeException exc) {
throw exc;
} catch (Exception exc) {
throw new RuntimeException("Wrapped in applyAndClose", exc);
}
}
(Поскольку ресурсы, которые необходимо закрыть, часто также генерируют исключения, когда они назначаются исключениями, отличными от времени выполнения, завершаются в исключениях времени выполнения, избегая необходимости в отдельном методе, который это делает.)
С помощью этого метода пример из вопроса выглядит следующим образом:
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
List<ImageView> collect = applyAndClose(photos::stream, s -> s
.map(photo -> new ImageView(new Image(String.valueOf(photo))))
.collect(Collectors.toList()));
Это полезно в ситуациях, когда требуется закрытие потока, например при использовании Files.lines
. Это также помогает, когда вам нужно выполнить "двойное закрытие", как в вашем примере в листинге 3.
Этот ответ является адаптацией старого ответа к аналогичному вопросу.