Java попробуй / поймай / наконец лучшие практики при получении / закрытии ресурсов - PullRequest
40 голосов
/ 04 ноября 2010

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

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) { }
}

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

Я пришел из C # фона,где я просто воспользуюсь блоком using, поэтому я не уверен, что «правильный» способ справиться с этим.

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

Ответы [ 8 ]

53 голосов
/ 04 ноября 2010

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

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 .
  • Когда вы выполняете закрытие или сброс потока выходного потока, существует вероятность «один раз в синей луне», что будет сгенерировано исключение из-за ошибок диска или переполнения файловой системы. Вы не должны раздавить это исключение! .

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

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

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

} finally {
    closeQuietly(oos);
}

(Другой ответ указывает на то, что метод closeQuietly уже доступен в библиотеке Apache Commons ... если вы не возражаете добавить в свой проект зависимость для метода из 10 строк. UPDATE : обратите внимание, что эти методы устарели в версии 2.6 API.)

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

1 - в использовании try-with-resources нет необходимости.


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

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

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

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


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

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

25 голосов
/ 26 ноября 2012

В настоящее время рекомендуется использовать try / catch / finally с объектами, которые могут быть закрыты (например, файлы), - использовать оператор try 7 with try, например:

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

13 голосов
/ 04 ноября 2010

Java 7 добавит Автоматическое управление ресурсами блоков. Они очень похожи на C # 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 голосов
/ 04 ноября 2010

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

public static void close(Closeable c) {
    if (c != null) {
        try {
            c.close();
        }
        catch (IOException e) {
            // ignore or log
        }
    }
}
3 голосов
/ 04 ноября 2010

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

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
}
1 голос
/ 04 ноября 2010

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

1 голос
/ 04 ноября 2010

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

0 голосов
/ 04 ноября 2010

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

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

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

...