この前の記事で、Scala の無名関数中での return 文の動きについてみてみた。
今回はその仕組みが Java 的にどうなっているのか見てみる。 JD-Plugin の導入には前回失敗したので、デコンパイルには Java Decompiler の GUI 版を使った。前回使ったコードはこれ。
def main(args:Array[String]) { println("main start") test(); println("main finish") } private def test() { val f = () => return; println("test start") f(); println("test finish") // 実行されない。 } ------------------ main start test start main finish
デコンパイル結果
上のコードをデコンパイルすると以下のようになる。
Java コードとしてはいろいろとおかしいところもあるようなのだが、
とりあえず雰囲気だけ確認してみる。
public void main(String[] args) { Predef..MODULE$.println("main start"); test(); Predef..MODULE$.println("main finish"); } public void test() { Object localObject = new Object(); try { Function0 f = new AbstractFunction0() { public static final long serialVersionUID = 0L; private final Object nonLocalReturnKey1$1; public final Nothing. apply() { throw new NonLocalReturnControl( this.nonLocalReturnKey1$1, BoxedUnit.UNIT); } }; Predef..MODULE$.println("test start"); f.apply(); Predef..MODULE$.println("test finish"); } catch (NonLocalReturnControl localNonLocalReturnControl) { if (localNonLocalReturnControl.key() != localObject) break label61; } ((BoxedUnit)localNonLocalReturnControl.value()); return; label61: throw localNonLocalReturnControl; }
12行目から匿名関数の定義が始まる。
Java では Function0 の匿名クラスが定義されている。
この匿名関数は Scala では return の一行なのだが、
Java コードの apply() では NonLocalReturnControl の throw に置き換えられている。
出力された Java コードでは匿名関数オブジェクトの 「nonLocalReturnKey1$1」に
値をセットするコードが見当たらないので今一つ良く解らないのだが、
最後に NonLocalReturnControl をキャッチして、
自関数内で定義した匿名関数からスローされた NonLocalReturnControl なら return 、
そうでなければキャッチした NonLocalReturnControl を再スローしている。
匿名関数中の return の仕組み
まとめてみよう。
- 匿名関数の定義時には ローカル変数の localObject を用意する。
- 匿名関数内の return は実際には localObject を持っている NonLocalReturnControl のスローである。
- 匿名関数を定義したら NonLocalReturnControl をキャッチして、
自スコープで定義した localObject を持っていたら return する。
そうでなければ 再スローする。
なるほど。匿名関数の定義元まで戻る仕組みはこうなっていたのか。
匿名関数呼び出しを try...catch でくくる
この仕組みを見ると匿名関数の呼び出しを try ... catch でくくると挙動が変わってしまう気がする。
やってみる。
def main(args:Array[String]) { println("main start") testx(); println("main finish") } private def testx() { val f = () => {return}; println("testx start") try { f(); // testx() を抜けるはずだった・・・ } catch { case t:Throwable => println("catched: " + t)} println("testx finish") // catchしなければ実行されなかった・・・ } ------------------ main start testx start catched: scala.runtime.NonLocalReturnControl testx finish main finish
f() 呼び出し後に Throwable をキャッチしたことによって、
f() 内で return が実行されても、testx() を脱出しなくなった。
匿名関数中の return 文を try...catch でくくる
匿名関数のなかで return 文を try...catch でくくるとどうなるだろう。
def main(args:Array[String]) { println("main start") testx2(); println("main finish") } private def testx2() { val f = () => { try return catch {case t:Throwable => println("catched: " + t)} }; println("testx2 start") f(); println("testx2 finish") // catchしなければ実行されなかった・・・ } ------------------ main start testx2 start catched: scala.runtime.NonLocalReturnControl testx2 finish main finish
この場合も、匿名関数 f() の実行時に NonLocalReturnControl がスローされなくなった結果、 testx2 から脱出しなくなった。
匿名関数と try...catch
匿名関数呼び出しや、匿名関数内の return 文を try...catch でくくって Throwable を拾うと、
脱出の挙動が変わってしまうことが分かった。
Scala のコード上は ただの return 文で Throwable とは一見、関係なさそうに見える。
このあたりは仕組みを理解して注意深く使わないと、
Scala のコードからだけだと思いがけない結果になるので注意しないといけない。
次回 は匿名関数を他の関数に引数で引き渡しているときのコードを見てみる。
0 件のコメント:
コメントを投稿