nullチェックは、ifですべきかtry/catchですべきか?

Javaの高速化の方法」というページに、次のような高速化手法が書かれていました。

文字列がNULLかどうかの判断は IF文を使用せずに 例外処理NullPointerException で置き換える

if文の場合です。

if(obj == null) { /* process */ }

try/catchの場合です。

try { /* process */ } catch (NullPointerException e) {}

たまにこういう謎の高速化手法を教えてもらうのですが、どうしてもすぐには信じられないので少し調べました。

ifとtry/catchのオーバーヘッドは?

Stack Overflowに、ドンピシャな質問がありました。
Java if vs. try/catch overhead
読んでみると、「例外処理は例外的な処理に使うものだから、通常のフローでnullになるような場合に使うのは良くない」というような回答が多いです。オーバーヘッドについて聞いてるんだけど、そもそもこれは比較するものではないという感じなのかもしれません。
ちなみに、最初に載せた高速化のページは、「あくまでパフォーマンスのため」という姿勢で書かれていますので、この辺の話はまた別です。
オーバーヘッドについては場合によるので、実際にやってみるべし、という感じかもしれません。

実際に試してみた

少し考えてみると、try/catchはNullPointerExceptionが発生しなければ比較演算が生じないので高速のように思います。しかし、NullPointerExceptionが発生した場合のcatchするコストは、ifの比較演算より重い処理のようにも思います。(根拠のなく感覚で書いています)
実際に試そうと思って書いてみましたが、コンパイル時に最適化がかかるせいか、シンプルには比較できませんでした。コンパイラを騙すために、乱数処理を混ぜています。
ifの場合です。

for (int i = 0; i < 1e8; i++) {
	Object nullObject = (Math.random() > 1e-9) ? null : new Object();
	if (nullObject == null)
		continue;
	nullObject.hashCode();
}

try/catchの場合です。

for (int i = 0; i < 1e8; i++) {
	Object nullObject = (Math.random() > 1e-9) ? null : new Object();
	try {
		nullObject.hashCode();
	} catch (NullPointerException e) {
	}
}

こんな感じの処理で、ifの場合とtry/catchの場合を、nullの場合とnullでない場合で比較しました。処理順に影響されないように順番を変えて2回実行した平均が以下の表です。

nullObject if try/catch
null 2.653 sec 2.778 sec
new Object() 11.556 sec 10.835 sec

結論、nullである確率が高い場合はifが高速だが、nullでない確率が高い場合はtry/catchの方が高速。しかしながら、普通に使う分にはほとんど誤差。

ifとtry/catchの違い

別のStack Overflowの質問を見たら、そもそもifとtry/catchでは挙動が変わる場合があることが書かれています。
try/catch vs null check in java

try{
  a.getB().getC().print();
}catch (NullPointerException e) {
}

これに対して「この例ではgetBメソッドや、getCメソッドの内部で発生したNullPointerExceptionまでcatchされてしまう」と書かれています。
つまり、try/catchした場合は、自分が呼び出したメソッドだけでなく、その更に内部で発生したNullPointerExceptionまでcatchしてしまうという違いがあります。
何か他に考えるべきことなどあったら教えて下さい。

タイトルとURLをコピーしました