Obsługa strumienia w Javie

2011-07-26 20:37

W językach kompilowanych mechanizmy wejścia-wyjścia opiera się niemal zawsze o koncepcję strumienia (stream), czyli abstrakcyjnego obiektu z którego możemy czytać dane i/lub je do niego zapisywać. Ponieważ strumień jest opakowaniem na jakiś zewnętrzny zasób – plik, połączenie sieciowe, itp. – należy generalnie dbać o jego poprawne i szybkie zamknięcie, gdy nie jest już potrzebny. Dotyczy to także zwłaszcza języków z zarządzaną pamięcią, gdzie osierocony obiekt strumienia może nie być posprzątany przez bardzo długi czas, zajmując zewnętrzne, niezarządzane zasoby systemowe.

Poprawny sposób postępowania z obiektem strumienia, który chciałem dzisiaj omówić, dotyczy konkretnie języka Java, gdyż tam cała sprawa jest co najmniej nietrywialna. Dzieje się tak z trzech powodów:

  • W Javie każdy wyjątek musi zostać albo złapany (catch), albo zadeklarowany jako opuszczający funkcję (throws). Jest to sprawdzane podczas kompilacji.
  • Metoda close, zamykająca strumień, deklaruje potencjalne wyrzucanie wyjątku IOException.
  • Java poniżej wersji 7 nie posiada odpowiednika konstrukcji with (obecnej np. w C# i Pythonie), która automatycznie posprzątałaby po obiekcie strumienia w momencie opuszczenia jej zasięgu.

Brak instrukcji with sprawia, iż do dyspozycji pozostaje nam wyłącznie try-catch lub try-finally. Naiwne zastosowanie któregoś z nich nie daje jednak pożądanych efektów:

  1. try {
  2.     InputStream is = new FileInputStream("file.txt");
  3.     // ...
  4. } finally {
  5.     is.close(); // ups!
  6. }

Takim efektem byłby na przykład fakt kompilowania się kodu :) W tej wersji jest to jednak niemożliwe (o ile funkcja nie deklaruje wyrzucania IOException), bowiem wyjątek ten może zostać rzucony przez metodę close… A przynajmniej taka jest teoria, którą kompilator niestety pedantycznie sprawdza.

W rzeczywistości ten kod ma przynajmniej jeszcze jeden błąd, którego nie wyeliminuje otoczenie wywołania close odpowiednim blokiem try-catch. Jego znalezienie pozostawiam aczkolwiek jako – ahem – ćwiczenie dla czytelnika ;) W zamian pokażę dla odmiany nieco lepszy sposób na obejście zaprezentowanych problemów.
Polega on na zastosowaniu dwóch zagnieżdżonych bloków try: jednego z catch do złapania IOException i drugiego z finally do zamknięcia strumienia. W całości prezentuje się to następująco:

  1. try {
  2.     InputStream is = new FileInputStream("file.txt");
  3.     try {
  4.         // (czytamy ze strumienia)
  5.     } finally {
  6.         is.close();
  7.     }
  8. } catch (IOException e) {
  9.     // ...
  10. }

Przy zastosowaniu takiej konstrukcji wszystkie miejsca, w których wyjątek I/O może wystąpić, są otoczone blokiem try-catch, więc kompilator nie będzie miał powodów do narzekań. Nadal też gwarantujemy, że strumień zostanie zawsze zamknięty, co z kolei zapewnia blok try-finally.

A że wygląda to wszystko cokolwiek nieestetycznie? Cóż… Java :)

Be Sociable, Share!
Be Sociable, Share!
Tags: , , , ,
Author: Xion, posted under Programming »


8 comments for post “Obsługa strumienia w Javie”.
  1. viroos:
    July 26th, 2011 o 21:13

    Co do “ćwiczenia dla czytelnika”, to chyba to będzie jakimś tam rozwiązaniem: http://download.java.net/jdk7/archive/b123/docs/api/java/lang/Throwable.html#getSuppressed%28%29.

  2. SebaS86:
    July 26th, 2011 o 21:29

    Ten jeszcze jeden błąd to po prostu zmienna w innym zasięgu.

  3. Xion:
    July 26th, 2011 o 21:36

    @SebaS86: Problem w tym, że nawet wyodrębnienie zmiennej do zasięgu wyżej nie wystarczy :)

  4. ktoso:
    July 26th, 2011 o 23:32

    Hej Xion,
    szybkie info/fix:

    Obecnie mamy już (dopiero? ;-)) “try with resources”,, vide: http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

    Kilka sub-standartów JDK7 jeszcze podlega głosowaniu, ale ta część akurat już sobie sprawnie działa i jest do pobrania.

  5. efgh:
    July 27th, 2011 o 0:53

    Jakby ludzie robili przykłady jak trzeba, to nie byłoby dyskusji.

    1. finally {
    2.     if(is != null)
    3.         is.close();
    4. }
  6. Xion:
    July 27th, 2011 o 7:15

    @ktoso: Szczęśliwi ci, którzy mogą używać Javy 7. Niestety wielu (np. programiści Androida) nie ma jeszcze tego luksusu :)

  7. Piotr Nowicki:
    August 1st, 2011 o 12:27

    google guava pozwala w bardziej estetyczny sposób zamykać strumienie:
    Closeables.closeQuietly(is);

  8. Jarek Przygódzki:
    September 17th, 2011 o 15:50

    Konstrukcja try-with-resources w Javie 7 zaproponowana swego czasu jako cześć Projektu Coin to sporo więcej niż tylko lukier składniowy – zapobiega między innymi bardzo niekorzystnemu zjawisku maskowania wyjątków
    http://www.oracle.com/technetwork/articles/java/trywithresources-401775.html

Comments are disabled.
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.