41

Javaでリーダーとストリームを使用することについて常に私を悩ませていることの1つは、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
    }
}

キャッチブロックが1つしかなく、よりエレガントに見えるので、最初の構造が好きです。実際に2番目または代替の構造を好む理由はありますか?

read更新:両方ともcloseIOExceptionsのみをスローすることを指摘した場合、違いはありますか?したがって、読み取りが失敗した場合、同じ理由で閉じることが失敗する可能性があります。

4

11 に答える 11

26

残念ながら、最初の例には大きな問題があります。つまり、読み取り時または読み取り後に例外が発生すると、finallyブロックが実行されるということです。ここまでは順調ですね。しかし、fr.close()それによって別の例外がスローされた場合はどうなるでしょうか。これは最初の例外を「打ち負かし」(ブロックを入れるのと少し似てreturnいます)、実際に問題を引き起こした原因に関するすべての情報を失います。finally

最終ブロックは次を使用する必要があります。

IOUtil.closeSilently(fr);

このユーティリティメソッドは次のことを行います:

public static void closeSilently(Closeable c) {
    try { c.close(); } catch (Exception e) {} 
} 
于 2008-10-08T16:09:47.400 に答える
7

私はいつも最初の例に行きます。

close が例外をスローする場合 (実際には、FileReader では決して発生しません)、呼び出し元に適切な例外をスローする処理の標準的な方法ではないでしょうか? クローズ例外は、リソースを使用して発生した問題よりもほぼ確実に優先されます。例外処理のアイデアが System.err.println を呼び出すことである場合は、おそらく 2 番目の方法が適切です。

どこまで例外をスローする必要があるかという問題があります。ThreadDeath は常に再スローされる必要がありますが、finally 内の例外はそれを停止します。同様に、Error は RuntimeException よりも先にスローし、RuntimeException はチェック済み例外よりも先にスローする必要があります。本当にそうしたいのであれば、これらのルールに従うコードを書き、それを「execute around」イディオムで抽象化することができます。

于 2008-10-08T16:31:53.457 に答える
4

私は2番目のものを好みます。なんで?と の両方が例外read()close()スローすると、そのうちの 1 つが失われる可能性があります。最初の構造では、 からの例外が からの例外をclose()オーバーライドしますがread()、2 番目の構造では、 からの例外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]);
    }
}

必要な try-catch は 1 つだけで、すべての例外を安全に処理できます。必要に応じてブロックを追加することもできfinallyますが、リソースを閉じる必要はありません。

于 2008-10-08T16:10:48.367 に答える
2

readcloseの両方が例外をスローする場合、readからの例外はオプション 1 で非表示になります。したがって、2 番目のオプションはより多くのエラー処理を行います。

ただし、ほとんどの場合、最初のオプションが優先されます。

  1. 多くの場合、例外が生成されたメソッドで例外を処理することはできませんが、その操作内でストリーム処理をカプセル化する必要があります。
  2. コードにライターを追加してみて、2 番目のアプローチがどれほど冗長になるかを確認してください。

生成されたすべての例外を渡す必要がある場合は、それを行うことができます

于 2008-10-08T16:37:27.360 に答える
1

私が見る限り、違いは、さまざまなレベルでさまざまな例外と原因があり、

キャッチ(例外e)

それを覆い隠します。複数のレベルの唯一のポイントは、例外を区別し、それらに対して何をするかです。

try
{
  try{
   ...
  }
   catch(IOException e)
  {
  ..
  }
}
catch(Exception e)
{
  // we could read, but now something else is broken 
  ...
}
于 2008-10-08T15:58:33.537 に答える
1

私は通常、次のことを行います。まず、テンプレート メソッド ベースのクラスを定義して、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);
                }
            }
        }
    }
}

「保留中」の例外に注意してください。これは、クローズ中にスローされた例外が、本当に気にかけている可能性のある例外をマスクする場合に対処します。

最終的には、装飾されたストリームの外側から最初に閉じようとします。そのため、FileWriter をラップする BufferedWriter がある場合は、最初に BufferedWriter を閉じようとし、それが失敗した場合でも、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 とそれが使用する「保留中」の変数アプローチに従うことを検討してください。

于 2008-10-08T16:56:53.623 に答える
0

ネストされた 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 が必要になる場合があります。

于 2008-10-08T19:52:22.270 に答える
0

@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); 
    } 
}
于 2008-10-08T23:43:56.437 に答える
0

ネストされた Try-Catch が避けられない場合があります。たとえば、エラー回復コード自体が例外をスローできる場合です。ただし、コードの可読性を向上させるために、ネストされたブロックを独自のメソッドにいつでも抽出できます。ネストされた Try-Catch-Finally ブロックのその他の例については、このブログ投稿を確認してください。

于 2015-04-15T00:04:43.493 に答える
0

私が使用する標準的な規則は、例外が 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()
    {}
}
于 2008-10-08T16:27:30.797 に答える
0

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 ブロックは、クローズを試みる前にリーダーが null でないかどうかを確認します。

同じパターンが Datasource、Connection、Statement、ResultSet にも当てはまります。

于 2008-10-08T17:58:32.143 に答える