Подтвердить что ты не робот

Java try/catch/окончательная передовая практика при приобретении/закрытии ресурсов

Во время работы над школьным проектом я написал следующий код:

FileOutputStream fos;
ObjectOutputStream oos;
try {
    fos = new FileOutputStream(file);
    oos = new ObjectOutputStream(fos);

    oos.writeObject(shapes);
} catch (FileNotFoundException ex) {
    // complain to user
} catch (IOException ex) {
    // notify user
} finally {
    if (oos != null) oos.close();
    if (fos != null) fos.close();
}

Проблема в том, что Netbeans сообщает мне, что строки resource.close() выдают IOException и поэтому должны быть либо пойманы, либо объявлены. Он также жалуется, что oos и fos еще не могут быть инициализированы (несмотря на нулевые проверки).

Это кажется немного странным, видя, как все дело в том, чтобы остановить IOException прямо там.

Мое исправление коленного сустава должно сделать это:

} finally {
    try {
        if (oos != null) oos.close();
        if (fos != null) fos.close();
    } catch (IOException ex) { }
}

Но в глубине души это беспокоит меня и чувствует себя грязным.

Я исхожу из фона С#, где я просто воспользуюсь блоком using, поэтому я не уверен, что "правильный" способ справиться с этим.

Каков правильный способ решения этой проблемы?

4b9b3361

Ответ 1

Если вы пытаетесь поймать и сообщить обо всех исключениях из источника, лучшим решением будет следующее:

ObjectOutputStream oos = null;
try {
   oos = new ObjectOutputStream(new FileOutputStream(file));
   oos.writeObject(shapes);
   oos.flush();
} catch (FileNotFoundException ex) {
    // complain to user
} catch (IOException ex) {
    // notify user
} finally {
    if (oos != null) {
        try {
            oos.close();
        } catch (IOException ex) {
            // ignore ... any significant errors should already have been
            // reported via an IOException from the final flush.
        }
    }
}

Примечания:

  • Стандартные потоки оболочек Java, считыватели и писатели распространяют close и flush на их обернутые потоки и т.д. Поэтому вам нужно только закрыть или очистить внешнюю оболочку.
  • Цель сброса явно в конце блока try заключается в том, что (реальный) обработчик для IOException получает вид сбоев записи 1.
  • Когда вы выполняете закрытие или завершение потока вывода, существует вероятность "один раз в синей луне", что исключение будет выбрано из-за ошибок диска или полной файловой системы. Вы не должны выдавать это исключение!

Если вам часто приходится "закрывать возможно пустой поток, игнорируя IOExceptions", вы можете написать себе вспомогательный метод, подобный этому:

public void closeQuietly(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (IOException ex) {
            // ignore
        }
    }
}

то вы можете заменить предыдущий блок finally следующим образом:

} finally {
    closeQuietly(oos);
}

(Еще один ответ указывает, что метод closeQuietly уже доступен в библиотеке Apache Commons... если вы не возражаете добавить зависимость от своего проекта для метода с 10 строками.).

Но будьте осторожны, чтобы использовать только closeQuietly в потоках, где исключения IO действительно неактуальны.

1 - Это не обязательно при использовании try-with-resources.


По вопросу flush() versus close(), о котором люди спрашивают:

  • Стандартные "фильтрующие" и "буферизованные" выходные потоки и писатели имеют контракт API, который утверждает, что close() вызывает сброс всех буферизованных выходных данных. Вы должны обнаружить, что все остальные (стандартные) классы вывода, которые выполняют буферизацию вывода, будут вести себя одинаково. Таким образом, для стандартного класса избыточно вызывать flush() непосредственно перед close().
  • Для пользовательских и сторонних классов вам необходимо изучить (например, прочитать javadoc, посмотреть код), но любой метод close(), который не очищает буферизованные данные, может быть нарушен.
  • Наконец, есть вопрос, что на самом деле делает flush(). Что говорит javadoc, это (для OutputStream...)

    Если предполагаемый пункт назначения этого потока является абстракцией, предоставляемой базовой операционной системой, например файлом, то очистка потока гарантирует, что только байты, ранее записанные в поток, передаются в операционную систему для записи; он не гарантирует, что они фактически записаны на физическое устройство, такое как дисковод.

    Итак... если вы надеетесь/предполагаете, что вызов flush() гарантирует, что ваши данные будут сохраняться, вы ошибаетесь! (Если вам нужно делать такие вещи, посмотрите на FileChannel.force метод...)


С другой стороны, если вы можете использовать Java 7 или более позднюю версию, лучшим решением будет "новый" источник try-in-ресурсов, как описано в ответе @Mike Clark.

Если вы не используете Java 7 или новее для своего нового кода, вы, вероятно, находитесь в яме и копаете глубже.

Ответ 2

Текущая лучшая практика для try/catch/finally с участием объектов, которые являются закрываемыми (например, Files), заключается в использовании инструкции Java 7 try-with-resource, например:

try (FileReader reader = new FileReader("ex.txt")) {
    System.out.println((char)reader.read());
} catch (IOException ioe) {
    ioe.printStackTrace();
}

В этом случае FileReader автоматически закрывается в конце инструкции try, без необходимости закрывать его в явном блоке finally. Здесь есть несколько примеров:

http://ppkwok.blogspot.com/2012/11/java-cafe-2-try-with-resources.html

Официальное описание Java:

http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html

Ответ 3

Java 7 добавит блоки Automatic Resource Management. Они очень похожи на С# using.

Джош Блох написал техническое предложение, которое я настоятельно рекомендую прочитать. Не только потому, что он даст вам преимущество на предстоящей языковой функции Java 7, а потому, что спецификация мотивирует необходимость такой конструкции и при этом иллюстрирует, как писать правильный код даже в отсутствие ARM.

Вот пример кода Аскера, переведенный в форму ARM:

try (FileOutputStream fos = new FileOutputStream(file);
        ObjectOutputStream oos = new ObjectOutputStream(fos)) 
{
    oos.writeObject(shapes);
}
catch (FileNotFoundException ex) 
{
    // handle the file not being found
}
catch (IOException ex) 
{
    // handle some I/O problem
}

Ответ 4

Обычно у меня есть небольшой класс IOUtil с таким методом, как:

public static void close(Closeable c) {
    if (c != null) {
        try {
            c.close();
        }
        catch (IOException e) {
            // ignore or log
        }
    }
}

Ответ 5

Как насчет этих парней? Нет нулевой проверки, не удивительно. Все очищается при выходе.

try {
    final FileOutputStream fos = new FileOutputStream(file);
    try {
        final ObjectOutputStream oos = new ObjectOutputStream(fos);
        try {
            oos.writeObject(shapes);
            oos.flush();
        }
        catch(IOException ioe) {
            // notify user of important exception
        }
        finally {
            oos.close();
        }
    }
    finally {
        fos.close();
    }
}
catch (FileNotFoundException ex) {
    // complain to user
}
catch (IOException ex) {
    // notify user
}

Ответ 6

К сожалению, поддержка языкового уровня отсутствует. Но есть много библиотек, которые делают это простым. Проверьте библиотеку commons-io. Или современный google-guava @http://guava-libraries.googlecode.com/svn/trunk/javadoc/index.html

Ответ 7

Вы делаете это правильно. Мне это тоже надоедает. Вы должны инициализировать эти потоки в явном виде - это общее соглашение. Все, что вы можете сделать, это присоединиться к клубу и пожелать using.

Ответ 8

Не прямой ответ на ваш вопрос, но это печальный факт, что, поскольку finally и catch связаны с try, люди думают, что они принадлежат друг другу. Наилучший дизайн для блоков try - либо иметь catch, либо finally, но не оба.

В этом случае ваши комментарии подсказывают, что что-то не так. Почему в методе, связанном с файлом IO, мы жалуемся на что-либо пользователю. Мы могли бы работать на сервере где-то с пользователем, который был бы в поле зрения.

Таким образом, код, который вы указываете выше, должен иметь finally, чтобы сбой изящно, когда все идет не так. В нем отсутствует способность справляться с ошибками, поэтому ваш catch находится где-то выше в цепочке вызовов.