第9章 Java 7 の機能を復習する : 問題 4 : 複数例外と共通のスーパークラス
問題
Java ライブラリを使用しているとき、複数例外をキャッチすることで恩恵を得られる状況に遭遇したライブラリは何か?
解答
JAXP ライブラリ(javax.xml のサブパッケージ)でありがたいと思った。
例えば、XML ファイルを読んで DOM オブジェクトを生成するケースである。
I/O エラーはともかく、XML の構文解析エラー(SAXException
)と滅多に起きない JAXP 環境の不備(ParserConfigurationException
)の各々にコードを書くのは嫌だ。
File xmlFile = ~; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(xmlFile); ... } catch (ParserConfigurationException | SAXException ex) { ... }
さらに問題
また、共通の例外となるスーパークラスにより恩恵を得られる状況に遭遇したライブラリは何か?
解答
何と言っても、Reflection を使ってメソッドを実行する際の ReflectiveOperationException
である。
例えば、クラス名とメソッド名が文字列で与えられたときに
- クラス名からデフォルト・コンストラクタでオブジェクトを生成し
- メソッド名から引数なしのメソッドを実行
するコードを書いてみる。
String className = ~; String methodName = ~; Class<?> clazz = Class.forName(className); // ClassNotFoundException Method method = clazz.getMethod(methodName); // NoSuchMethodException Object object = clazz.newInstance(); // InstantiationException // IllegalAccessException method.invoke(object); // InvocationTargetException // IllegalAccessException
4行のコードで何と5種類のチェック例外が起こる。
ところがReflectiveOperationException
は、これら5種類のチェック例外の親である。
■ ReflectiveOperationException
を使用
String className = ~; String methodName = ~; try { Class<?> clazz = Class.forName(className); Method method = clazz.getMethod(methodName); Object object = clazz.newInstance(); method.invoke(object); } catch (ReflectiveOperationException ex) { ... }
第9章 Java 7 の機能を復習する : 問題 3 : 複数例外のキャッチ
問題
複数の例外をキャッチするcatch
節の中でその例外を再度スローする場合に、その処理が書かれているメソッドでのthrows
宣言はどのように宣言すればよいか?
次の例で答えよ。
public void process() throws ??? { try { ... } catch (FileNotFoundException | UnknownHostException ex) { logger.log(Level.SEVERE, "...", ex); throw ex; } }
解答
普通に、2つの例外をthrows
宣言に書けばよい。
■ 普通の解答
public void process() throws FileNotFoundException, UnknownHostException {
あと、FileNotFoundException
とUnknownHostException
はIOException
の派生クラスなので、IOException
でまとめることも可。
■ ひとつにまとめる
public void process() throws IOException {
第9章 Java 7 の機能を復習する : 問題 2 : 抑制された例外
問題
以下のコードを try-with-resources 文を使わず、かつclose
メソッドでスローされる例外を抑制された例外として元の例外に追加するように実装せよ。
■ try-with-resources 文を使った例
try (BufferedReader in = Files.newBufferedReader(Paths.get("/usr/share/dict/words")); BufferedWriter out = Files.newBufferedWriter(Paths.get("/tmp/out.txt")) ) { String line; while ((line = in.readLine()) != null) { out.write(line.toLowerCase()); out.newLine(); } }
解答
IOException
がスローされるのは、大きく分けて以下の4つのケースである。
in
ないしout
のオープン- ファイルを読み書きするメインパート
out
のクローズin
のクローズ
そこで、これら4つのケースで発生した例外を覚えておくことにする。
IOException openException = null; // リソースのオープンで発生した例外 IOException mainException = null; // 中心となる例外 IOException inCloseException = null; // in.close() で発生した例外 IOException outCloseException = null; // out.close() で発生した例外 try { BufferedReader in; try { in = Files.newBufferedReader(Paths.get("/usr/share/dict/words")); } catch (IOException ex) { openException = ex; throw ex; } try { BufferedWriter out; try { out = Files.newBufferedWriter(Paths.get("/tmp/out.txt")); } catch (IOException ex) { openException = ex; throw ex; } try { String line; while ((line = in.readLine()) != null) { out.write(line.toLowerCase()); out.newLine(); } } catch (IOException ex) { mainException = ex; throw ex; } finally { try { out.close(); } catch (IOException ex) { outCloseException = ex; throw ex; } } } finally { try { in.close(); } catch (IOException ex) { inCloseException = ex; throw ex; } } } catch (IOException ex) { // ex は openException, mainException, inCloseException, outCloseException のいずれか if (openException != null) { // mainException, outCloseException は発生していない if (inCloseException != null) openException.addSuppressed(inCloseException); throw openException; } if (mainException != null) { if (inCloseException != null) mainException.addSuppressed(inCloseException); if (outCloseException != null) mainException.addSuppressed(outCloseException); throw mainException; } if (inCloseException != null && outCloseException != null) { inCloseException.addSuppressed(outCloseException); throw inCloseException; } throw ex; }
なんというステップ数、可読性は最悪。
try-with-resources 文は偉大だ!
第9章 Java 7 の機能を復習する : 問題 1 : try-with-resources 文
問題
以下のコードを try-with-resources 文を使わないで実装せよ。
try (Scanner in = new Scanner(Paths.get("/usr/share/dict/words")); PrintWriter out = new PrintWriter("/tmp/out.txt") ) { while (in.hasNext()) out.println(in.next().toLowerCase()); }
実装に当たっては、Scanner
とPrintWriter
両方がオープンされた場合に両方のリソースをきちんとクローズすること。
この中には、Exception
がスローされる個所が7つある。
Scanner
のコンストラクタPrintWriter
のコンストラクタScanner
のhasNext
メソッドScanner
のnext
メソッドPrintWriter
のprintln
メソッドScanner
のclose
メソッドPrintWriter
のclose
メソッド
ちょっと待った!
この問題は間違っている。
Scanner
とPrintWriter
クラスは、ほとんどのメソッドでチェック例外をスローしない。
Scanner
クラスは、内部に持っているReadable
オブジェクトがスローする例外を、PrintWriter
クラスは、内部に持っているWriter
オブジェクトがスローする例外を、隠蔽している。
そこで、各クラスは隠蔽した例外を公開するためにScanner#ioException
とPrintWriter#checkError
メソッドを用意しているぐらいである。
■ 実際に発生する例外(イタリックは非チェック例外)
クラス | メソッド | 例外 |
---|---|---|
Scanner |
コンストラクタ | IOException |
PrintWriter |
コンストラクタ | FileNotFoundException SecurityException |
Scanner |
hasNext |
IllegalStateException |
Scanner |
next |
NoSuchElementException IllegalStateException |
PrintWriter |
println |
なし |
Scanner |
close |
なし |
PrintWriter |
close |
なし |
何のことはない、例外をスローするのはコンストラクタだけである。
close
メソッドが例外をスローしないんじゃ、try-with-resources 文のありがたみが薄れる。
Scanner
, PrintWriter
の替わりにBufferedReader
, BufferedWriter
を使うことにする。
■ try-with-resources 文を使った例
try (BufferedReader in = Files.newBufferedReader(Paths.get("/usr/share/dict/words")); BufferedWriter out = Files.newBufferedWriter(Paths.get("/tmp/out.txt")) ) { String line; while ((line = in.readLine()) != null) { out.write(line.toLowerCase()); out.newLine(); } }
解答
try-with-resources 文を使わないとしたら、try-finally 句をネストすればよい。
■ try-with-resources 文を使わない
BufferedReader in = Files.newBufferedReader(Paths.get("/usr/share/dict/words")); try { BufferedWriter out = Files.newBufferedWriter(Paths.get("/tmp/out.txt")); try { String line; while ((line = in.readLine()) != null) { out.write(line.toLowerCase()); out.newLine(); } } finally { out.close(); } } finally { in.close(); }
ただし、次の練習問題のために、抑制された例外には対応していない。
第8章 その他の Java 8 機能を理解する : 問題 16 : 正規表現の名前付きキャプチャグループ
問題
市(city)、州(state)、郵便番号(zip code)を含む行を解析するために、名前付きキャプチャグループを用いた正規表現を使用せよ。
郵便番号は、5桁と9桁(5桁ハイフン4桁)の両方を受け付けるようにせよ。
解答
本文には、市(city)と州(state)の2つをピックアップする名前付きキャプチャグループの正規表現を使っていた。
■ 市(city)と州(state)の2つだけの正規表現
(?<city>[\p{L} ]+),\s*(?<state>[A-Z]{2})
つまり、" で囲まない CSV フォーマットを想定しているんですね。
じゃ、郵便番号(zip code)を追加すると、正規表現は次のようになる。
■ 市(city)と州(state)と郵便番号(zip code)の正規表現
(?<city>[\p{L} ]+),\s*(?<state>[A-Z]{2})\s*,\s*(?<zipCode>[0-9]{5}(-[0-9]{4})?)
■ 市、州、郵便番号を含む行の解析
Path path = Paths.get("cities.txt"); Pattern pattern = Pattern.compile( "(?<city>[\\p{L} ]+),\\s*(?<state>[A-Z]{2})\\s*,\\s*(?<zipCode>[0-9]{5}(-[0-9]{4})?)" ); try { for (String line : Files.readAllLines(path)) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) continue; System.out.print("City is " + matcher.group("city")); System.out.print(", State is " + matcher.group("state")); System.out.println(", Zip Code is " + matcher.group("zipCode")); } } catch (IOException ex) { ex.printStackTrace(); }
第8章 その他の Java 8 機能を理解する : 問題 15 : 簡易 grep
問題
Files.lines
とPattern#asPredicate
を使用して、与えられた正規表現に一致するすべての行を表示する grep のようなプログラムを書け。
解答
機能をいろいろ盛り込みたいところだけど、ここは簡潔に以下のような仕様とする。
- 第1引数は正規表現の式、第2引数はファイルのパス
CASE_INSENSITIVE
などのオプションは用意しない
埋め込みフラグ表現(?i
)を使うこと- ファイルのパスにディレクトリは指定できない
- ファイルのパスにワイルドカードは使えない
- 単にマッチした行を標準出力に出力するだけ
■ main メソッド
public static void main(String[] args) { // コマンドライン引数のチェック if (args.length < 2) { System.out.println("Usage : grep <regular expression> <file path>"); return; } // コマンドライン引数から正規表現とファイルのパスを取得 Pattern pattern = Pattern.compile(args[0]); Path path = Paths.get(args[1]); // 検索 try (Stream<String> stream = Files.lines(path)) { stream.filter(pattern.asPredicate()) .forEach(System.out::println); } catch (IOException ex) { ex.printStackTrace(); } }
実質、3ステップで出来ちゃうんですね。
不思議の国のアリスから "Wonderland" を検索してみると…
■ 実行結果
> grep (?i)wonderland alice.txt Project Gutenberg's Alice's Adventures in Wonderland, by Lewis Carroll Title: Alice's Adventures in Wonderland *** START OF THIS PROJECT GUTENBERG EBOOK ALICE'S ADVENTURES IN WONDERLAND *** ALICE'S ADVENTURES IN WONDERLAND Wonderland, though she knew she had but to open them again, and all with the dream of Wonderland of long ago: and how she would feel with End of Project Gutenberg's Alice's Adventures in Wonderland, by Lewis Carroll *** END OF THIS PROJECT GUTENBERG EBOOK ALICE'S ADVENTURES IN WONDERLAND ***