著作一覧 |
久々にJavaでHTTPクライアントを書いて、そういえば1.4.2の頃はコネクションタイムアウトすら設定できなかったからSocketを使ったなぁとか思い出しながらJDKのドキュメントを眺めているとURLConnectionが随分とまともになっているようで感心しがてら使うことにした。
が、好事魔多し。ついtry with resourcesが使えるので次のように書いて失敗した。
urlconn.setDoOutput(true); urlconn.connect(); String response = null; try (OutputStream os = urlconn.getOutputStream(); InputStream is = urlconn.getInputStream(); ByteArrayOutputStream bao = new ByteArrayOutputStream()) { os.write(outputData.getBytes("UTF-8")); os.flush(); byte[] buff = new byte[4096]; for (;;) { int len = is.read(buff); if (len < 0) break; if (len > 0) bao.write(buff, 0, len); } response = bao.toString("UTF-8"); } ...
とても不思議だった(が、わかればそれはそうかも知れないとは思うが、ろくなAPIとも思えない)。
あと、こういう処理を考えると、"UTF-8"と文字列でCharsetを指定するよりも、Charsetのインスタンスをあらかじめ用意して使いまわしたいのだが、ByteArrayOutputStreamのtoStringがStringを引数に受けるものしか用意していないのが中途半端に感じた。
public class Foo { public final String bar; ... }
つまり、finalフィールドとすることで読み込み専用のフィールドとして外部に使わせたい(ついでに誤った更新をコンパイル時に弾く)ということだ。
で、コンストラクタを書く。
public Foo(Object o) { try { bar = o.getClass().getMethod("getBar", null).invoke(o, null); } catch (Exception e) { throw new IllegalArgumentException("wrong argument, should has getBar:" + o); } }
するとコンパイラが、barを初期化しないパスがあると文句を垂れる。初期化していないというのはデタラメ千万で、仕様によりフィールドの初期値はnullと決まっている。仕様を無視して文句を垂れるのだから最近のコンパイラのお節介っぷりにはびっくりだ(追記:ふと気づいて仕様を眺めたらfinal fieldについては must be definitely assigned at the end of every constructor of the class in which it is declaredなのでおれが無知だった)。
実際にはcatch (Exception e)ではなくずらずらリフレクション関係の例外を複数の節に書いているので、ExceptionとかThrowableでcatchしてthrowすればそのパスはあり得なくなるから、文句は垂れられなかったのかも。
が、コンパイラ用のスィッチとか調べるのが面倒なのでしょうがないので明示的に設定してやった。
public Foo(Object o) { bar = null; // for shutting up the stupid compiler try { bar = o.getClass().getMethod("getBar", null).invoke(o, null); } catch (Exception e) { throw new IllegalArgumentException("wrong argument, should has getBar:" + o); } }
すると今度は、重複した初期化というコンパイルエラーとなった。まあそれはそれほど悪いエラーではないが、デフォルト警告で十分だろう(あくまでもスィッチを調べる気にはならないのだが、本当にあるのかなぁ)。
まともにつきあうのがいやになったので、finalを削除しておしまい。
追記:
仕様通りの動作であるならば、記述方法を変えるしかない。というわけで、次のように書くのが正解なのだろう。
ctr() { String localFoo = null; try { } catch () { } foo = localFoo; // コンストラクタの最後にローカル変数を利用して初期設定。 }
ジェズイットを見習え |
「ExceptionとかThrowableでcatchしてthrowすればそのパスはあり得なくなるから、文句は垂れられなかったのかも」が正解だと思います。