web-dev-qa-db-fra.com

Pourquoi Files.lines (et les flux similaires) ne sont-ils pas fermés automatiquement?

Le javadoc pour Stream indique:

Les flux ont une méthode BaseStream.close () et implémentent AutoCloseable, mais il n’est pas nécessaire de fermer pratiquement toutes les instances de flux après leur utilisation. En règle générale, seuls les flux dont la source est un canal IO (tels que ceux renvoyés par Files.lines (Path, Charset)) doivent être fermés. La plupart des flux sont sauvegardés par des collections, des tableaux ou des fonctions génératrices, qui ne nécessitent aucune gestion de ressources particulière. (Si un flux nécessite une fermeture, il peut être déclaré ressource dans une instruction try-with-resources.) 

Par conséquent, la grande majorité du temps, les flux peuvent être utilisés dans une seule ligne, comme collection.stream().forEach(System.out::println);, mais pour Files.lines et les autres flux sauvegardés par des ressources, vous devez utiliser une instruction try-with-resources ou fuir des ressources.

Cela me semble inutile et source d'erreurs. Comme les flux ne peuvent être itérés qu'une seule fois, il me semble qu'il n'y a pas de situation où la sortie de Files.lines ne devrait pas être fermée dès qu'elle a été itérée. L'implémentation devrait donc simplement appeler implicitement la fermeture à la fin d'un terminal opération. Est-ce que je me trompe?

43
MikeFHay

Oui, c'était une décision délibérée. Nous avons considéré les deux alternatives. 

Le principe de conception est le suivant: "quiconque acquiert la ressource doit la libérer". Les fichiers ne se ferment pas automatiquement lorsque vous lisez à EOF; nous nous attendons à ce que les fichiers soient fermés explicitement par quiconque les a ouverts. Les flux qui sont sauvegardés par les ressources IO sont les mêmes.

Heureusement, le langage fournit un mécanisme pour automatiser cela pour vous: essayez avec les ressources. Étant donné que Stream implémente AutoCloseable, vous pouvez effectuer les opérations suivantes:

try (Stream<String> s = Files.lines(...)) {
    s.forEach(...);
}

L'argument selon lequel "il serait très pratique de se fermer automatiquement pour que je puisse l'écrire comme un one-liner" est agréable, mais serait surtout la queue qui remue le chien. Si vous avez ouvert un fichier ou une autre ressource, vous devez également être prêt à le fermer. Une gestion des ressources efficace et cohérente l'emporte "Je veux écrire cela sur une seule ligne", et nous avons choisi de ne pas fausser la conception simplement pour préserver le principe d'une ligne. 

55
Brian Goetz

J'ai un exemple plus spécifique en plus de la réponse @BrianGoetz. N'oubliez pas que la Stream a des méthodes d'échappement telles que iterator(). Supposons que vous faites ceci:

Iterator<String> iterator = Files.lines(path).iterator();

Après cela, vous pourrez appeler plusieurs fois hasNext() et next(), puis abandonnez simplement cet itérateur: L’interface Iterator prend parfaitement en charge une telle utilisation. Il n’ya aucun moyen de fermer explicitement la Iterator, le seul objet que vous pouvez fermer ici est la Stream. Donc, de cette façon, cela fonctionnerait parfaitement bien:

try(Stream<String> stream = Files.lines(path)) {
    Iterator<String> iterator = stream.iterator();
    // use iterator in any way you want and abandon it at any moment
} // file is correctly closed here.
15
Tagir Valeev

En plus si vous voulez "écrire une ligne". Vous pouvez simplement faire ceci:

Files.readAllLines(source).stream().forEach(...);

Vous pouvez l'utiliser si vous êtes sûr d'avoir besoin de l'intégralité du fichier et qu'il est petit. Parce que ce n'est pas une lecture paresseuse.

4
Michael Starodynov

Si vous êtes paresseux comme moi et que cela ne vous dérange pas "si une exception est levée, le descripteur de fichier reste ouvert", vous pouvez envelopper le flux dans un flux à fermeture automatique, comme ceci (il peut exister d'autres méthodes):

  static Stream<String> allLinesCloseAtEnd(String filename) throws IOException {
    Stream<String> lines = Files.lines(Paths.get(filename));
    Iterator<String> linesIter = lines.iterator();

    Iterator it = new Iterator() {
      @Override
      public boolean hasNext() {
        if (!linesIter.hasNext()) {
          lines.close(); // auto-close when reach end
          return false;
        }
        return true;
      }

      @Override
      public Object next() {
        return linesIter.next();
      }
    };
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.DISTINCT), false);
  }
1
rogerdpack