Есть ли чистый способ дать объекту Java область действия с уничтожением?

Представьте, что MyOpenedFile — это некий файл-обертка с открытыми потоками. Затем предположим, что этот код:

// method in an Util class
static void safeClose(MyOpenedFile f) {
  if (f != null) {
    try {
      f.close();
    } catch(IOException ex) { /* add logging or console output */ }
  }
}

Фактический метод для вопроса:

void doSomeFileOperation(...) throws IOException, ... {
    MyOpenedFile f1 = null;
    MyOpenedFile f2 = null;
    try {

      /* method's "business logic" code beings */
      f1 = new MyOpenedFile(...);
      //do stuff
      f2 = new MyOpenedFile(...);
      // do stuff
      f1.close(); f1 = null;
      // do stuff with f1 closed
      f2.close(); f2 = null;
      // do stuff with f2 closed
      /* method's "business logic" code ends */

    } finally {
      Util.safeClose(f1); f1 = null;
      Util.safeClose(f2); f2 = null; 
    }
}

Теперь это довольно беспорядочно и особенно подвержено ошибкам (например, некоторый код в блоке finally может быть очень трудно вызвать в модульных тестах). Например, в C++ деструктор позаботится об очистке (будь то вызов деструктора указателя с областью действия или напрямую), и код будет намного чище.

Итак, есть ли лучший/лучше/чище способ обернуть приведенный выше фрагмент кода бизнес-логики, чтобы любые исключения распространялись, но оба файла f1 и f2 закрывались (или, по крайней мере, предпринималась попытка закрытия обоих, даже если это не удается)?

Также приветствуются ответы, указывающие на любые библиотеки с открытым исходным кодом, такие как Apache Commons, предоставляющие хорошие оболочки.


person hyde    schedule 01.11.2012    source источник
comment
кстати, в вашем примере кода есть ошибка, которая фактически приведет к утечке дескрипторов файлов, потому что вы установили f1=null перед его закрытием.   -  person Chii    schedule 01.11.2012
comment
@Chii Нет, насколько я понимаю. Есть close или safeClose перед всеми null заданиями.   -  person hyde    schedule 01.11.2012
comment
Почему вы вызываете close дважды для каждого файла (в попытке и в конце)?   -  person home    schedule 01.11.2012
comment
@дома нет. Мне просто нужна фиктивная бизнес-логика, описывающая случай, который закрывает файл и устанавливает ссылку на ноль. Если ссылка в блоке finally пуста, с ней ничего не делается.   -  person hyde    schedule 01.11.2012
comment
@hyde: ах, я неправильно понял твой образец. Что интересно, потому что очень легко неправильно прочитать код, написанный в таком стиле, в котором логика перемежается с уборкой. Но тогда вы не можете переписать его из-за требования, чтобы f1 была закрыта, а f2 оставалась открытой (в противном случае вы могли бы переписать образец таким образом, чтобы логика была передана, а служебные операции были спрятаны в другом месте).   -  person Chii    schedule 01.11.2012
comment
Я отредактировал пример, чтобы использовать пользовательский класс файлов. В реальном коде, вероятно, будут использоваться стандартные классы File и Stream, но я думаю, что это делает приведенный выше код немного чище.   -  person hyde    schedule 01.11.2012
comment
Я не понимаю, почему вам нужно специально закрывать файлы (потоки?) Перед тем, как что-то делать. Если вам нужно, чтобы он был записан в файл, вы можете просто использовать flush(), не обязательно закрывать f1 или f2, прежде чем вы закончите с методом. Кроме того, если этот класс MyOpenedFile написан вами, вы можете легко заставить его вызывать только close() в базовом потоке при первом вызове, устраняя необходимость в методе safeClose.   -  person Thor84no    schedule 01.11.2012
comment
Например, может быть что-то, что занимает много времени в последней части бизнес-логики (например, выполнение HTTP-запроса), и файл закрывается перед этим, чтобы освободить ресурсы ОС как можно раньше. Иногда его можно оптимизировать, иногда нет, это не относится к более чистому общему решению (и в Java7 есть).   -  person hyde    schedule 01.11.2012


Ответы (4)


Файл — это оболочка для строки, которая содержит имя файла, которое может существовать или не существовать. Он не имеет состояния, поэтому вам не нужно его закрывать.

Ресурс, который вам нужно закрыть, — это FileInputStream или BufferedReader, и вы можете закрыть их неявно с помощью ARM в Java 7.

try(BufferedReader br = new BufferedReader(new FileReader(file))) {

}

Это закроет br, когда он выйдет из блока.

http://www.oracle.com/technetwork/articles/java/trywithresources-401775.html

person Peter Lawrey    schedule 01.11.2012
comment
Я решил принять ваш ответ за то, что он был наиболее точным, и с хорошим образцом кода IMO (иногда маленький - это красиво), спасибо. Теперь все, что мне осталось, это выяснить, могу ли я использовать Java7 для реальных целей... :-/ - person hyde; 01.11.2012

Ознакомьтесь с утверждением try-with-resources, которое закрыть ресурсы после завершения блока try.

Используемый вами класс File не похож на java.io.File, потому что в нем нет метода close(). В этом случае убедитесь, что ваш собственный класс File реализует Closeable, чтобы он работал с ARM.

try (FileInputStream f1 = new FileInputStream("test1.txt");
     FileInputStream f2 = new FileInputStream("test2.txt")) {
    // Some code
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
person maba    schedule 01.11.2012
comment
Спасибо, что указали на File, я изменил имя класса и добавил комментарий к вопросу, объясняющему логику имени пользовательского класса. - person hyde; 01.11.2012
comment
Одна придирка, больше похожая на общие разглагольствования, не направленные на вас: я думаю, что такие блоки catch следует использовать очень редко. Почти всегда плохая идея просто поймать исключение, зарегистрировать его и продолжить, как будто ничего не произошло. Если исключение можно безопасно игнорировать, его регистрация просто беспорядок. Если необходимо предпринять какие-либо действия, то это должно быть нечто большее, чем просто регистрация (например, всплывающее диалоговое окно в приложении с графическим интерфейсом). В большинстве случаев следует разрешить распространение исключения, иногда в качестве причины другого более важного исключения. Простое ведение журнала полезно в основном, когда программист не уверен, что нужно сделать правильно. - person hyde; 01.11.2012

Вам не нужно закрывать файлы (которые представляют собой файлы в файловой системе), как указано здесь:

Нужно ли закрывать файлы, которые я выполняю File. getName() включен?

Я предполагаю, что вы спрашиваете больше о файловых потоках/читателях?

В этом случае в Java 7 есть хорошая новая функция: http://www.vineetmanohar.com/2011/03/java-7-try-with-auto-closable-resources/

Если вы работаете над более старой версией Java, я бы просто упростил это:

void doSomeFileOperation(...) throws IOException, ... {
  FileInputStream f1 = null;
  FileInputStream f2 = null;
  try {

    // do stuff

  } finally {
    Util.safeClose(f1); 
    Util.safeClose(f2); 
  }
}
person Bruce Lowe    schedule 01.11.2012

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

Другое дело, что образец, который у вас есть, делает много дополнительных, ненужных шагов.

void doSomeFileOperation(...) throws IOException, ... {
    File f1 = null;
    File f2 = null;
    try {
      f1 = new File(...);
      f2 = new File(...);
      // callback to another class / method that does the real work
    } finally {
      Util.safeClose(f1);
      Util.safeClose(f2);
    }
}

Вам не нужно устанавливать для экземпляров File значение null. Если вы попытаетесь их использовать, вы получите исключение.

Мне не интересно, какой File объект вы используете. Стандартный класс File в java не имеет метода close().

person Augusto    schedule 01.11.2012
comment
Сначала я думал так же, но недостаток в том, что требование, чтобы // do stuff with f1 closed больше не сохранялось. Вам нужно будет передать два разных обратных вызова, и это не решит проблему. - person Chii; 01.11.2012
comment
Да, в зависимости от конкретного бизнес-кода очистку можно оптимизировать. Но это приводит к различному шаблонному коду для разных случаев, что может быть даже хуже. - person hyde; 01.11.2012