Czy poprawnie używam Java 7 try-with-resources

87

Oczekuję, że buforowany czytnik i czytnik plików zostaną zamknięte, a zasoby zwolnione, jeśli wyjątek zostanie zgłoszony.

public static Object[] fromFile(String filePath) throws FileNotFoundException, IOException
{
    try (BufferedReader br = new BufferedReader(new FileReader(filePath)))
    {
        return read(br);
    } 
}

Czy jednak istnieje wymóg posiadania catchklauzuli umożliwiającej skuteczne zamknięcie?

EDYTOWAĆ:

Zasadniczo powyższy kod w Javie 7 jest odpowiednikiem poniższego kodu w Javie 6:

public static Object[] fromFile(String filePath) throws FileNotFoundException, IOException
{

    BufferedReader br = null;

    try
    {
        br = new BufferedReader(new FileReader(filePath));

        return read(br);
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        try
        {
            if (br != null) br.close();
        }
        catch(Exception ex)
        {
        }
    }

    return null;
}
Gepard
źródło
Po ponownym przeczytaniu pytania nie jestem pewien, czy dobrze je rozumiem. Czy możesz to wyjaśnić?
Maroun
Cześć. Cheetah, próbuję zrozumieć rolę pierwszego catchz twojego przykładu dla Java 6. Tj. catch (Exception ex) { throw ex; }- to po prostu ponowne wyrzucenie wyjątku, nic nie robi, można go łatwo usunąć bez żadnego bólu. A może coś mi brakuje?
Sasha,

Odpowiedzi:

103

Jest poprawna i nie ma wymogu catchklauzuli. Dokument Oracle java 7 mówi, że zasób zostanie zamknięty niezależnie od tego, czy wyjątek zostanie faktycznie zgłoszony, czy nie.

catchKlauzuli należy używać tylko wtedy, gdy chcesz zareagować na wyjątek. catchKlauzula zostanie wykonana po zasób jest zamknięta.

Oto fragment z samouczka Oracle :

Poniższy przykład odczytuje pierwszy wiersz z pliku. Używa wystąpienia BufferedReader do odczytu danych z pliku. BufferedReader to zasób, który należy zamknąć po zakończeniu programu z nim:

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
} // In this example, the resource declared in the try-with-resources statement is a BufferedReader.

... Ponieważ instancja BufferedReader jest zadeklarowana w instrukcji try-with-resource, zostanie zamknięta niezależnie od tego, czy instrukcja try zakończy się normalnie czy nagle (w wyniku wyrzucenia IOException przez metodę BufferedReader.readLine).

EDYTOWAĆ

Odnośnie nowego zredagowanego pytania:

Kod w Javie 6 wykonuje blok, catcha następnie finally. Powoduje to, że zasoby są nadal potencjalnie otwarte w catchbloku.

W 7 składni Javy, zasoby są zamknięte przed tym catchblokiem, więc zasoby są już zamknięte podczas catchwykonywania bloku. Jest to udokumentowane w powyższym linku:

W instrukcji try-with-resources każdy blok catch lub final jest uruchamiany po zamknięciu zadeklarowanych zasobów.

tak
źródło
69

Korzystanie z zasobów typu „try-with-resources” będzie działać dobrze w tym konkretnym przypadku, ale ogólnie nie jest całkiem poprawne. Nie powinieneś łączyć zasobów w taki sposób, ponieważ może to prowadzić do niemiłych niespodzianek. Załóżmy, że masz zmienny rozmiar bufora:

public static Object[] fromFile(String filePath) throws FileNotFoundException, IOException
{
    int sz = /* get buffer size somehow */
    try (BufferedReader br = new BufferedReader(new FileReader(filePath), sz))
    {
        return read(br);
    } 
}

Załóżmy, że coś poszło nie tak i skończyłeś z sznegatywnym wynikiem . W takim przypadku Twój zasób plikowy (utworzony przez new FileReader(filePath)) NIE zostanie zamknięty.

Aby uniknąć tego problemu, należy określić każdy zasób osobno w następujący sposób:

public static Object[] fromFile(String filePath) throws FileNotFoundException, IOException
{
    int sz = /* get buffer size somehow */
    try (FileReader file = new FileReader(filePath);
         BufferedReader br = new BufferedReader(file, sz))
    {
        return read(br);
    } 
}

W tym przypadku, nawet jeśli inicjalizacja brawarii filenadal zostanie zamknięta. Więcej szczegółów znajdziesz tutaj i tutaj .

Andrii Polunin
źródło
Próbuję zrozumieć, dlaczego zasób utworzony przez new FileReader(filePath))nie zamyka się w przypadku IllegalArgumentExceptionwyrzucenia an, gdy sz jest ujemne. Czy próba z zasobami nie zamyka wszystkich AutoClosablezasobów niezależnie od rzuconych wyjątków?
Prasoon Joshi
3
@PrasoonJoshi Nie, wywołuje tylko .close()zmienne, które zostały zadeklarowane w inicjatorze try-with-resources. Dlatego w tym przykładzie rozdzielenie go na dwie deklaracje załatwia sprawę.
Mario Carneiro
4
Andrii i @Mario Macie rację i mylicie się. W pierwszym przykładzie FileReader nie jest zamykany przez logikę try-with-resource. Ale gdy BufferedReader zostanie zamknięty, zamknie również opakowany FileReader. Aby uzyskać dowód, spójrz na źródło java.io.BufferedReader.close (). W konsekwencji należy preferować kod z pierwszego przykładu, ponieważ jest bardziej zwięzły.
jschreiner
7
@jschreiner Prawda, chociaż problem Andrii (nieco wymyślony), w którym sz < 0konstruktor wyrzuca wyjątek, w rzeczywistości spowoduje wyciek zasobu.
Mario Carneiro
5
@mario Zgadzam się. Zewnętrzny konstruktor może się nie powieść, a zasób wewnętrzny zostałby przeciekany. Nie widziałem tego wcześniej, dziękuję.
jschreiner