著作一覧 |
Rubyは君を信用するが、Javaは君を信用しない。
それは設計ポリシーで、それ自体は悪いことではない。
どう信用していないかと言えば、まず最初に上げられるのは、無名inner classのメソッド内で参照する外側のローカル変数はfinalとして再代入を許さないことだ。
次が、ラムダ式用に用意されているjava.util.functionの使いにくさ=throws Exceptionが無い定義だ。
これらは、保存されて処理された場合を考慮すれば当然とも言える。
class Foo { Consumer<int> callback; public void addConsumer(Consumer<int> cb) { callback = cb; } private foofoofoo() { callback.accept(bar); }
というオブジェクトに対して、仮に、accpetがthrows Exceptionであったなら
try { foo.addConsumer(n -> 例外が起きる可能性); } catch (Exception e) { ... }
と書いてもなんの意味もない。acceptがいついかなるタイミングで呼び出されるかわからない以上、例外をcatchできないからだ。そんな不親切なインターフェイスはありえないので、Consumerのacceptにthrows Exceptionを付けることはできない。当然acceptの中に例外ハンドラを実装すべきだ。
でも、それはConsumerという名前によって明らかにされている。であれば、汎用の関数的インターフェイスとして
public interface Callback{ void callback(T t) throws Exception; }
はどうだろうか。callbackという名前は、その呼び出しに対して呼ばれた側からの呼び返しで、コンシューマとは異なり、呼び出し中の呼び返しが保証される場合にのみ使うという規約で問題ない。
が、Javaは開発者を信用しないから、規約を無視して、コールバックという名前の範囲を超えた呼び出しにも利用できてしまうと考える。したがって、捕捉されない例外のスローと成りえる可能性を持つかな?
いや、そうでもない。class Fooの実装で
private foofoofoo() { try { callback.accept(bar); } catch (Exception e) { } }
と書かなければエラーとなるからだ。が、こちら側は例外を握り潰すか、上位に伝播させるより他に元の呼び出し側の意図を反映する機会がない。
結局、意図せざる動作をする可能性があるから、Consumer側の実装で例外を処理する必要がある。となるのだろう。
面倒な話だ。
ジェズイットを見習え |