web-dev-qa-db-fra.com

Java 8 flux et essayer avec des ressources

Je pensais que l'API Stream était là pour rendre le code plus facile à lire. J'ai trouvé quelque chose d'assez ennuyeux. L'interface Stream étend l'interface Java.lang.AutoCloseable.

Donc, si vous souhaitez fermer correctement vos flux, vous devez utiliser try with resources.

Listing 1. Pas très sympa, les ruisseaux ne sont pas fermés.

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

Listing 2. Avec 2 essais imbriqués

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

Listing. Comme map renvoie un flux, les fonctions stream() et map() doivent être fermées.

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

L'exemple que je donne n'a aucun sens. J'ai remplacé Path en images jpg par Integer, pour les besoins de l'exemple. Mais ne vous laissez pas distraire par ces détails.

Quelle est la meilleure façon de contourner ces flux à fermeture automatique. Je dois dire que je ne suis satisfait d'aucune des 3 options que j'ai présentées. Qu'est-ce que tu penses? Existe-t-il encore d'autres solutions plus élégantes?

22
fan

Vous utilisez @SuppressWarnings("resource") qui supprime vraisemblablement un avertissement concernant une ressource non fermée. Ce n'est pas l'un des avertissements émis par javac. Les recherches sur le Web semblent indiquer qu'Eclipse émet des avertissements si un AutoCloseable n'est pas fermé.

Ceci est un avertissement raisonnable selon la spécification Java 7 qui a introduit AutoCloseable:

Une ressource qui doit être fermée lorsqu'elle n'est plus nécessaire.

Cependant, la spécification Java 8 pour AutoCloseable a été assouplie pour supprimer la clause "doit être fermé". Il dit maintenant, en partie,

Un objet qui peut contenir des ressources ... jusqu'à ce qu'il soit fermé.

Il est possible, et en fait courant, pour une classe de base d'implémenter AutoCloseable même si toutes ses sous-classes ou instances ne contiennent pas de ressources libérables. Pour le code qui doit fonctionner en général, ou lorsqu'il est connu que l'instance AutoCloseable nécessite une libération de ressources, il est recommandé d'utiliser des constructions try-with-resources. Cependant, lorsque vous utilisez des fonctionnalités telles que Stream qui prennent en charge à la fois les formulaires basés sur les E/S et les non basés sur les E/S, les blocs try-with-resources ne sont généralement pas nécessaires lors de l'utilisation de formulaires non basés sur les E/S.

Cette question a été longuement débattue au sein du groupe d'experts Lambda; ce message résume la décision. Entre autres choses, il mentionne des changements à la spécification AutoCloseable (citée ci-dessus) et à la spécification BaseStream (citée par d'autres réponses). Il mentionne également la nécessité éventuelle d'ajuster l'inspecteur de code Eclipse pour la sémantique modifiée, sans doute pour ne pas émettre des avertissements sans condition pour les objets AutoCloseable. Apparemment, ce message n'a pas été transmis aux gens d'Eclipse ou ils ne l'ont pas encore changé.

En résumé, si les avertissements Eclipse vous font penser que vous devez fermer tous les objets AutoCloseable, c'est incorrect. Seuls certains objets AutoCloseable spécifiques doivent être fermés. Eclipse doit être corrigé (s'il ne l'a pas déjà fait) pour ne pas émettre d'avertissement pour tous les objets AutoCloseable.

30
Stuart Marks

Vous n'avez besoin de fermer les flux que si le flux doit effectuer un nettoyage de lui-même, généralement des E/S. Votre exemple utilise un HashSet, il n'a donc pas besoin d'être fermé.

à partir du Stream javadoc:

En général, seuls les flux dont la source est un canal IO (tels que ceux renvoyés par Files.lines (Path, Charset)) devront être fermés. La plupart des flux sont soutenus par des collections, des tableaux ou des fonctions de génération , qui ne nécessitent aucune gestion particulière des ressources.

Donc, dans votre exemple, cela devrait fonctionner sans problème

List<ImageView> collect = photos.stream()
                       .map(photo -> ...)
                       .collect(toList());

[~ # ~] modifier [~ # ~]

Même si vous avez besoin de nettoyer des ressources, vous devriez pouvoir utiliser un seul essai avec ressource. Imaginons que vous lisez un fichier où chaque ligne du fichier est un chemin d'accès à une image:

 try(Stream<String> lines = Files.lines(file)){
       List<ImageView> collect = lines
                                  .map(line -> new ImageView( ImageIO.read(new File(line)))
                                  .collect(toList());
  }
12
dkatzel

"Fermable" signifie "peut être fermé" et non "doit être fermé".

C'était vrai dans le passé, par ex. voir ByteArrayOutputStream :

La fermeture d'un ByteArrayOutputStream n'a aucun effet.

Et cela est vrai maintenant pour Streams où le la documentation le précise :

Les flux ont une méthode BaseStream.close() et implémentent AutoCloseable, mais presque toutes les instances de flux n'ont pas besoin d'être fermées après utilisation. Généralement, seuls les flux dont la source est un canal IO (tels que ceux renvoyés par Files.lines(Path, Charset))) devront être fermés.

Donc, si un outil d'audit génère de faux avertissements, c'est un problème de l'outil d'audit, pas de l'API.

Notez que même si vous souhaitez ajouter la gestion des ressources, il n'est pas nécessaire d'imbriquer les instructions try. Bien que ce qui suit soit suffisant:

final Path p = Paths.get(System.getProperty("Java.home"), "COPYRIGHT");
try(Stream<String> stream=Files.lines(p, StandardCharsets.ISO_8859_1)) {
    System.out.println(stream.filter(s->s.contains("Oracle")).count());
}

vous pouvez également ajouter le Stream secondaire à la gestion des ressources sans try supplémentaire:

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());
}
9
Holger

Il est possible de créer une méthode utilitaire qui ferme de manière fiable les flux avec une instruction try-with-resource-statement.

C'est un peu comme un essai final qui est une expression (ce qui est le cas par exemple dans 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);
    }
}

(Étant donné que les ressources qui doivent être fermées lèvent souvent des exceptions lorsqu'elles sont allouées, les exceptions non liées à l'exécution sont encapsulées dans des exceptions d'exécution, évitant ainsi le besoin d'une méthode distincte qui le fasse.)

Avec cette méthode, l'exemple de la question ressemble à ceci:

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

Ceci est utile dans les situations où la fermeture du flux est requise, comme lors de l'utilisation de Files.lines. Cela aide également lorsque vous devez faire une "double fermeture", comme dans votre exemple dans Listing 3 .

Cette réponse est une adaptation d'un ancienne réponse à une question similaire.

3
Lii