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

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

Одна из вещей, которая всегда вызывает у меня проблемы с использованием 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
    }
}

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

UPDATE: изменилось бы это, если бы я указал, что оба read и close генерируют только IOExceptions? Поэтому мне кажется, что если сбой чтения не удастся, то по той же причине произойдет сбой.

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

IOUtil.closeSilently(fr);

где только этот метод утилиты:

public static void closeSilently(Closeable c) {
    try { c.close(); } catch (Exception e) {} 
} 

Ответ 3

Я предпочитаю второй. Зачем? Если оба исключения 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, если хотите, но нет необходимости закрывать ресурсы.

Ответ 4

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

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

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

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

Ответ 5

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

catch (исключение e)

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

try
{
  try{
   ...
  }
   catch(IOException e)
  {
  ..
  }
}
catch(Exception e)
{
  // we could read, but now something else is broken 
  ...
}

Ответ 6

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

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 и "ожидающий" переменный подход, который он использует.

Ответ 7

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

Это связано с тем, что если исключение уже распространяется, исключение, исключенное из блока finally, превзойдет исходное исключение (и, следовательно, будет потеряно).

В 99% случаев это не то, что вы хотите, поскольку исходное исключение, вероятно, является источником вашей проблемы (любые вторичные исключения могут быть побочными эффектами от первого, но будут скрывать вашу способность находить источник исходного исключения и таким образом, реальная проблема).

Итак, ваш базовый код должен выглядеть так:

try
{
    // Code
}
// Exception handling
finally
{
    // Exception handling that is garanteed not to throw.
    try
    {
         // Exception handling that may throw.
    }
    // Optional Exception handling that should not throw
    finally()
    {}
}

Ответ 8

Второй подход.

В противном случае я не вижу, чтобы вы ловили исключение из конструктора 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 проверяет, не читает ли читатель NOT, прежде чем пытаться закрыть.

То же самое относится к источнику данных, соединению, Statement, ResultSet.

Ответ 9

Иногда вложенный 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.

Ответ 10

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

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

Я бы всегда использовал logger (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); 
    } 
}

Ответ 11

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