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

StackOverflow https://stackoverflow.com/questions/183499

  •  06-07-2019
  •  | 
  •  

Вопрос

Одна из вещей, которая всегда мешает мне использовать 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, и он кажется более элегантным. Есть ли причина фактически предпочесть вторую или альтернативную конструкцию?

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

IOUtil.closeSilently(fr);

где этот метод утилиты просто делает:

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

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

<Ч>

Начиная с Java 7, try-with- Конструкция ресурсов делает это намного проще. Читать, не обращая внимания на исключения:

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 , если хотите, но нет необходимости закрывать ресурсы.

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

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

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

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

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

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

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

    Я обычно делаю следующее. Во-первых, определите класс, основанный на шаблонном методе, чтобы справиться с беспорядком 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);
                    }
                }
            }
        }
    }
    

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

    Оператор finally пытается сначала закрыть снаружи любого декорированного потока, поэтому, если у вас есть 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 и "qunding; pending" " переменный подход, который он использует.

    Стандартное соглашение, которое я использую, состоит в том, что вы не должны позволять исключениям выходить из блока 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()
        {}
    }
    

    Мне нравится подход @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); 
        } 
    }
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top