Потоки 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.

Этот ответ является адаптацией старого ответа к аналогичному вопросу.