문제

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만 발생시키나요?따라서 읽기가 실패하면 같은 이유로 닫기도 실패할 것 같습니다.

도움이 되었습니까?

해결책

나는 항상 첫 번째 예를 살 것입니다.

Close가 예외를 던졌다면 (실제로 파일러드에게는 결코 일어나지 않을 예정), 발신자에게 적합한 예외를 던지는 표준 처리 방법은 없습니까? 가까운 예외는 거의 확실히 자원을 사용했던 모든 문제를 능가합니다. 예외 처리 아이디어가 System.err.println을 호출하는 것이면 두 번째 방법이 더 적합 할 것입니다.

예외를 얼마나 멀리 던져야하는지에 대한 문제가 있습니다. Threaddeath는 항상 다시 시작되어야하지만, 마침내 예외는 마침내 그것을 막을 것입니다. 마찬가지로 Or 정말로 원한다면 코드를 작성 하여이 규칙을 따르고 "Execute Around"관용구로 추상화 할 수 있습니다.

다른 팁

첫 번째 예제에는 큰 문제가있는 것이 두렵습니다. 이는 읽기에서 또는 후에 예외가 발생하면 finally 블록 실행. 여태까지는 그런대로 잘됐다. 그러나 만약 그렇다면 fr.close() 그러면 또 다른 예외가 발생합니까? 이것은 첫 번째 예외를 "트럼프"할 것입니다 ( return 안에 finally 블록) 및 실제로 문제를 일으킨 원인에 대한 모든 정보를 잃게됩니다. 우선 첫째로.

마지막으로 블록을 사용해야합니다.

IOUtil.closeSilently(fr);

이 유틸리티 방법은 다음과 같습니다.

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

나는 두 번째를 선호합니다.왜?둘 다라면 read() 그리고 close() 예외를 던지면 그 중 하나가 손실될 수 있습니다.첫 번째 건설에서는 예외가 적용됩니다. close() 다음의 예외를 재정의합니다. read(), 두 번째에서는 예외가 있습니다. close() 별도로 처리됩니다.


자바 7부터는 리소스를 활용한 시도 구성 이것을 훨씬 더 간단하게 만듭니다. 예외를 신경쓰지 않고 읽으려면:

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]);
    }
}

try-catch는 하나만 필요하며 모든 예외를 안전하게 처리할 수 있습니다.여전히 추가할 수 있습니다. finally 원한다면 차단할 수 있지만 리소스를 닫을 필요는 없습니다.

둘 다 읽다 그리고 닫다 예외, 예외를 던지십시오 읽다 옵션 1에 숨겨져 있습니다. 따라서 두 번째 옵션은 더 많은 오류 처리를합니다.

그러나 대부분의 경우 첫 번째 옵션이 여전히 선호됩니다.

  1. 대부분의 경우, 생성 된 방법의 예외를 다룰 수는 없지만 해당 작업 내에서 스트림 처리를 여전히 캡슐화해야합니다.
  2. 코드에 작가를 추가하고 두 번째 접근 방식이 어떻게되는지 확인하십시오.

생성 된 모든 예외를 통과 해야하는 경우 할 수 있습니다.

내가 볼 수있는 한 차이는 다른 수준에서 다른 예외와 원인이 있다는 것입니다.

캐치 (예외 E)

그것을 모호하게합니다. 다중 레벨의 유일한 요점은 예외를 구별하는 것입니다.

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

나는 보통 다음을 수행합니다. 먼저, 시도/캐치 혼란을 처리하기 위해 템플릿-방법 기반 클래스를 정의하십시오.

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

"보류중인"예외에 주목하십시오. 이것은 Close 동안 발생하는 예외가 우리가 실제로 신경 쓰는 예외를 숨길 경우를 처리합니다.

마지막으로 장식 된 스트림의 외부에서 닫으려고 시도하므로 버퍼링 라이터가 파일 리터를 감싸고 있다면 먼저 버퍼링 라이터를 닫으려고 노력하고 있으며 실패한 경우 여전히 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
}

이 접근법을 사용하면 시도/캐치/마침내 닫는 파일을 다시 처리하기 위해 걱정할 필요가 없습니다.

이것이 당신이 사용하기에 너무 무겁다면, 적어도 시도/캐치와 그것이 사용하는 "보류중인"가변 접근법을 따르는 것에 대해 생각하십시오.

내가 사용하는 표준 규칙은 예외가 마침내 블록을 피해서는 안된다는 것입니다.

예외가 이미 이미 전파되는 경우 마지막으로 블록에서 벗어난 예외가 원래의 예외 (따라서 손실 될 수 있음)를 능가하기 때문입니다.

99%의 경우 원래 예외가 문제의 원인이기 때문에 원하는 것이 아닙니다 (2 차 예외는 첫 번째의 부작용 일 수 있지만 원래 예외의 출처를 찾는 능력을 가릴 수 있습니다. 문제).

따라서 기본 코드는 다음과 같습니다.

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

두 번째 접근.

그렇지 않으면, 나는 당신이 Filereader 생성자의 예외를 잡는 것을 보지 못합니다.

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

Public Filereader (String filename)는 filenotfoundException을 던졌습니다

그래서 나는 보통 시도 블록 내부에 생성자가 있습니다. 마지막으로 블록은 독자가 닫히려고 시도하기 전에 무효가 아닌지 확인합니다.

DataSource, Connection, Statement, Resultset에서도 동일한 패턴이 사용됩니다.

때로는 중첩 트리 캐치가 선호하지 않습니다. 이것을 고려하십시오.

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

이것은 아마도 나쁜 예일 것입니다. 그러나 당신은 중첩 된 시도 팩치가 필요한 시간이 있습니다.

나는 @chris Marshall의 접근 방식을 좋아하지만, 예외가 조용히 삼키는 것을보고 싶지 않습니다. 예외를 기록하는 것이 가장 좋다고 생각합니다.

나는 항상 유틸리티 클래스를 사용하여 이런 종류의 일반적인 예외를 처리하지만,이를 그의 대답과 다르게 만들 것입니다.

나는 항상 로거 (로그 4J)를 사용하여 오류를 기록합니다.

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