Есть ли предпочтение для вложенных блоков try / catch? - PullRequest
39 голосов
/ 08 октября 2008

Одна из вещей, которая всегда мешает мне использовать Readers and Streams в Java, это то, что метод close() может вызвать исключение. Поскольку хорошая идея поместить метод close в блок finally, для этого требуется немного неловкая ситуация. Я обычно использую эту конструкцию:

FileReader fr = new FileReader("SomeFile.txt");
try {
    try {
        fr.read();
    } finally {
        fr.close();
    }
} catch(Exception e) {
    // Do exception handling
}

Но я также видел эту конструкцию:

FileReader fr = new FileReader("SomeFile.txt");
try {
    fr.read() 
} catch (Exception e) {
    // Do exception handling
} finally {
    try {
        fr.close();
    } catch (Exception e) {
        // Do exception handling
    }
}

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

ОБНОВЛЕНИЕ: Будет ли иметь значение, если я укажу, что и read, и close только генерируют исключения IOException? Так что мне кажется вероятным, что если чтение не удастся, закрыть не удастся по той же причине.

Ответы [ 11 ]

26 голосов
/ 08 октября 2008

Боюсь, что в первом примере есть большая проблема: если во время или после чтения возникает исключение, выполняется блок finally. Все идет нормально. Но что, если fr.close() вызывает другое исключение? Это «превзойдет» первое исключение (немного похоже на помещение return в блок finally) и . Вы потеряете всю информацию о том, что на самом деле привело к возникновению проблемы .

Ваш блок finally должен использовать:

IOUtil.closeSilently(fr);

, где этот служебный метод просто делает:

public static void closeSilently(Closeable c) {
    try { c.close(); } catch (Exception e) {} 
} 
7 голосов
/ 08 октября 2008

Я бы всегда пошел за первый пример.

Если бы close выкинул исключение (на практике это никогда не произойдет для FileReader), разве стандартным способом обработки не было бы выбрасывание исключения, соответствующего вызывающей стороне? Закрытое исключение почти наверняка превзойдет любую проблему, возникшую при использовании ресурса. Второй метод, вероятно, более уместен, если ваша идея обработки исключений заключается в вызове System.err.println.

Существует проблема того, как далеко должны быть выброшены исключения. ThreadDeath всегда должен быть переброшен, но любое исключение внутри наконец остановит это. Точно так же Ошибка должна бросить дальше, чем RuntimeException и RuntimeException дальше, чем проверенные исключения. Если вы действительно хотите, вы можете написать код, который будет следовать этим правилам, а затем абстрагировать его с помощью выражения «выполнить вокруг».

3 голосов
/ 08 октября 2008

Я предпочитаю второй. Зачем? Если и read(), и close() выдают исключения, одно из них может быть потеряно. В первой конструкции исключение из close() заменяет исключение из read(), а во второй исключение из close() обрабатывается отдельно.


Начиная с Java 7, конструкция try-with-resources делает это намного проще. Для чтения без заботы об исключениях:

try (FileReader fr = new FileReader("SomeFile.txt")) {
    fr.read();
    // no need to close since the try-with-resources statement closes it automatically
}

С обработкой исключений:

try (FileReader fr = new FileReader("SomeFile.txt")) {
    fr.read();
    // no need to close since the try-with-resources statement closes it automatically
} catch (IOException e) {
    // Do exception handling
    log(e);
    // If this catch block is run, the FileReader has already been closed.
    // The exception could have come from either read() or close();
    // if both threw exceptions (or if multiple resources were used and had to be closed)
    // then only one exception is thrown and the others are suppressed
    // but can still be retrieved:
    Throwable[] suppressed = e.getSuppressed(); // can be an empty array
    for (Throwable t : suppressed) {
        log(suppressed[t]);
    }
}

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

2 голосов
/ 08 октября 2008

Если оба значения read и close выдают исключение, исключение из read будет скрыто в варианте 1. Таким образом, второй вариант больше обрабатывает ошибки .

Однако в большинстве случаев первый вариант все еще будет предпочтительным.

  1. Во многих случаях вы не можете иметь дело с исключениями в методе, который они генерируют, но вы все равно должны инкапсулировать обработку потока в этой операции.
  2. Попробуйте добавить писателя в код и посмотрите, насколько многословен второй подход.

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

1 голос
/ 08 октября 2008

Я обычно делаю следующее. Сначала определите класс, основанный на методе шаблона, чтобы справиться с беспорядком try / catch

import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public abstract class AutoFileCloser {
    private static final Closeable NEW_FILE = new Closeable() {
        public void close() throws IOException {
            // do nothing
        }
    };

    // the core action code that the implementer wants to run
    protected abstract void doWork() throws Throwable;

    // track a list of closeable thingies to close when finished
    private List<Closeable> closeables_ = new LinkedList<Closeable>();

    // mark a new file
    protected void newFile() {
        closeables_.add(0, NEW_FILE);
    }

    // give the implementer a way to track things to close
    // assumes this is called in order for nested closeables,
    // inner-most to outer-most
    protected void watch(Closeable closeable) {
        closeables_.add(0, closeable);
    }

    public AutoFileCloser() {
        // a variable to track a "meaningful" exception, in case
        // a close() throws an exception
        Throwable pending = null;

        try {
            doWork(); // do the real work

        } catch (Throwable throwable) {
            pending = throwable;

        } finally {
            // close the watched streams
            boolean skip = false;
            for (Closeable closeable : closeables_) {
                if (closeable == NEW_FILE) {
                    skip = false;
                } else  if (!skip && closeable != null) {
                    try {
                        closeable.close();
                        // don't try to re-close nested closeables
                        skip = true;
                    } catch (Throwable throwable) {
                        if (pending == null) {
                            pending = throwable;
                        }
                    }
                }
            }

            // if we had a pending exception, rethrow it
            // this is necessary b/c the close can throw an
            // exception, which would remove the pending
            // status of any exception thrown in the try block
            if (pending != null) {
                if (pending instanceof RuntimeException) {
                    throw (RuntimeException) pending;
                } else {
                    throw new RuntimeException(pending);
                }
            }
        }
    }
}

Обратите внимание на «ожидающее исключение» - оно учитывает случай, когда исключение, выбрасываемое при закрытии, маскирует исключение, о котором мы могли бы действительно заботиться.

Наконец, вначале пытается закрыться снаружи любого декорированного потока, поэтому, если у вас есть BufferedWriter, обертывающий FileWriter, мы сначала пытаемся закрыть BuffereredWriter, и если это не удается, все равно попробуйте закрыть сам FileWriter. *

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

try {
    // ...

    new AutoFileCloser() {
        @Override protected void doWork() throws Throwable {
            // declare variables for the readers and "watch" them
            FileReader fileReader = null;
            BufferedReader bufferedReader = null;
            watch(fileReader = new FileReader("somefile"));
            watch(bufferedReader = new BufferedReader(fileReader));

            // ... do something with bufferedReader

            // if you need more than one reader or writer
            newFile(); // puts a flag in the 
            FileWriter fileWriter = null;
            BufferedWriter bufferedWriter = null;
            watch(fileWriter = new FileWriter("someOtherFile"));
            watch(bufferedWriter = new BufferedWriter(fileWriter));

            // ... do something with bufferedWriter
        }
    };

    // .. other logic, maybe more AutoFileClosers

} catch (RuntimeException e) {
    // report or log the exception
}

Используя этот подход, вам никогда не придется беспокоиться о try / catch / finally, чтобы снова иметь дело с закрытием файлов.

Если это слишком тяжело для вашего использования, по крайней мере подумайте о том, чтобы придерживаться подхода try / catch и подхода с использованием «отложенных» переменных, который он использует.

1 голос
/ 08 октября 2008

Разница, насколько я вижу, состоит в том, что существуют разные исключения и причины в игре на разных уровнях, и

поймать (исключение е)

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

try
{
  try{
   ...
  }
   catch(IOException e)
  {
  ..
  }
}
catch(Exception e)
{
  // we could read, but now something else is broken 
  ...
}
0 голосов
/ 15 апреля 2015

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

0 голосов
/ 09 октября 2008

Мне нравится подход @Chris Marshall, но мне никогда не нравится, когда исключения молча поглощаются. Я думаю, что лучше регистрировать исключения, особенно если вы продолжаете в любом случае.

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

Я бы всегда использовал регистратор (log4j для меня) для регистрации ошибок и т. Д.

IOUtil.close(fr);

Небольшая модификация служебного метода:

public static void close(Closeable c) {
    try {
      c.close();
    } catch (Exception e) {
      logger.error("An error occurred while closing. Continuing regardless", e); 
    } 
}
0 голосов
/ 08 октября 2008

Иногда вложенный try-catch не является предпочтением, учтите это:

try{
 string s = File.Open("myfile").ReadToEnd(); // my file has a bunch of numbers
 // I want to get a total of the numbers 
 int total = 0;
 foreach(string line in s.split("\r\n")){
   try{ 
     total += int.Parse(line); 
   } catch{}
 }
catch{}

Это, вероятно, плохой пример, но иногда вам понадобится вложенный try-cactch.

0 голосов
/ 08 октября 2008

2-й подход.

В противном случае я не вижу, чтобы вы ловили исключение из конструктора FileReader

http://java.sun.com/j2se/1.5.0/docs/api/java/io/FileReader.html#FileReader(java.lang.String)

public FileReader (String fileName) выдает FileNotFoundException

Итак, у меня обычно также есть конструктор внутри блока try. Блок finally проверяет, не является ли читатель нулевым, прежде чем пытаться сделать закрытие.

Тот же шаблон применяется для источника данных, соединения, оператора, ResultSet.

...