読者です 読者をやめる 読者になる 読者になる

C Sharpens you up

http://qiita.com/yuba に移しつつあります

返す値を一時変数に受けるよりはtry-finally

Java

余計な一時変数というのはなるたけ作りたくないものですが、例えばJavaでこんなコード書いちゃうことあるじゃないですか。

static T poll() {
  Integer result = array[top];                   // (1)
  if (++top >= array.length) top -= array.length;// (2)
  return result;
}

メソッドの戻り値が決まる(1)のが処理完了(2)より前というパターン。Delphiなら無縁な手間なんです*1が、C系の言語だとメソッド抜けるときまで戻り値を一時変数に受けておかないといけません。

こんな無意味な一時変数美しくありませんね。どこか行ってほしいですね。そして幸い、Javaにはそのための道具があります。try-finallyです。
最初のコードはこんなコードに書き換えることが可能です。

static T poll() {
  try { return array[top]; }
  finally { if (++top >= array.length) top -= array.length; }
}

finallyといえばリソースの解放というのが定番ですが、主処理をfinallyの中に突っ込むことで戻り値を確定させたあとに主処理という順番が自然に記述できました。

重くない?

try構文と言えばオーバーヘッドが気になる感覚は健全です。一時変数がいやだという美意識のためにパフォーマンスを犠牲にするのは本末転倒。

しかし本当にパフォーマンスに悪影響があるのか、それは計測してみてから言うこと。実際、ぶん回してみましょう。

一時変数版 6946 ms
try-finally版 6935 ms

結果を見ると、ほとんど差はありません。おそらく誤差の範囲内ですがtry-finallyを使った方がわずかに速いくらい。

実のところ、try構文で気になるオーバーヘッドの正体は、tryブロックの実現ではなくその中で発生する例外オブジェクトの生成コストです。だから、NullPointerExceptionをtry-catchするよりはnullチェックを書けと言い習わされている理由はNullPointerExceptionインスタンスを生成するコストを嫌ってのことです。nullに遭遇する頻度が少ないならむしろtry-catchにした方が条件分岐が減る分パフォーマンスはむしろ向上します*2

というわけで、try-finally方式を使うにあたってパフォーマンスを気にする必要はありません。

注意点は、例外の握りつぶし

finallyブロックの使い方には注意点があります。それはfinallyブロック内で例外を飛ばした場合、tryブロックの返した戻り値または発生させた例外が握りつぶされてしまうという点。finallyブロックでreturnを書いてしまった場合も同様です。

finallyに主処理を書くなら、そこから外には絶対に例外を投げないという保証が必要です。

(2014-10-16追記)C#では

計測してみると、C#ではtry-finally版の方が有意に遅いです。

*1:処理のどの段階ででも、関数名と同名の変数に代入しておいた値が抜けたときに戻り値になります。

*2:http://blog.katty.in/4119