Javaは静的型付け言語です。
つまり、変数は宣言したときに型が決定されますし
呼び出すメソッドは予め定義されていなければいけません。
これをある程度解決するために用意されているのがリフレクション(Reflection)です。
Reflectionを使うことによって、使用するクラスやメソッドを動的に決定することが出来るようになります。
しかし、これは非常にリスクのある方法だということを良く知っておく必要があります。
Reflectionのメリットは以下の一点に尽きるでしょう。
これは、コードの変更無しにロジックをカスタマイズできる事を意味します。
しかし、数々のデメリットがあります。
これはJavaの持つ静的言語としてのメリットを消してしまう事になります。
例えば、動的なメソッドを実行するためには一般的に以下のようなコードを記述します。
Class<?> clazz = Class.forName("SampleClass");
Method method = clazz.getMethod("someMethod", new Class[] { Integer.class });
method.invoke(target, Integer.valueOf(1));
このコードを実行すると、発生する可能性のある例外は以下の通りです。
ClassNotFoundException / SecurityException / NoSuchMethodExceptionIllegalArgumentException / IllegalAccessException / InvocationTargetException
…Reflectionを使ったことが無い人の為に言っておくと、これは何のジョークでもなく真実です。
これだけの数の例外をcatchして適切な処理をするのは、不可能に近いのです。
ほとんどの人は、これらの例外をまとめてExceptionでキャッチしてしまいます。
これはアンチパターンとして知られている「Javaでやってはいけない事」なのです。
もう少し解っている人は、SecurityException / IllegalArgumentException は
RuntimeExceptionのサブクラスなので、catch節を記述する必要が無いことに気付くはずです。
それでもまだ残り4つ。
一番簡単かつ実践的な方法は、これらの例外を全て IllegalStateException などの
RuntimeException でthrowし直すことです。
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
システム共通の独自Exceptionがあればそれを使っても良いでしょう。
このようにすれば、catchする例外を無くす、もしくは減らすことが出来ます。
Reflectionは動的にクラスやメソッドを決定するので、
当然実行するまでどのメソッドが呼び出されるか判りません。
これは机上デバッグが出来ないだけでなく、
デバッグ実行でも流れを追いにくいという欠点になります。
僕は、これこそがReflection最大のデメリットだと思っています。
言い換えれば、これによって保守性が大幅に低下するのです。
これを見れば、メリットよりもデメリットの方が多いという事に気付くはずです。
しかし、それでもどうしてもReflectionを使わなければならない場面というのはあります。
よく検討して、他に方法が見当たらない場合のみReflectionを使うようにしましょう。
よく言われる事として、Reflectionは処理速度が遅いという点が指摘されます。 しかし、僕はこれをデメリットだとは思っていません。 なぜなら、Reflectionはそれほど頻繁に呼び出される処理ではないからです。 1回の処理で1000回以上もReflectionを使うような事が無い限り、 Reflectionによる速度低下はほぼ無視していい程小さいというのが事実です。 もちろん、気になる人は実際にプロファイリングしてその結果を確かめましょう。
Reflectionと同様にある種の柔軟性を与えてくれるものに、マッピングがあります。
以下に挙げる2つのコードを比較してみて下さい。
Pattern 1
bean.setName("jon");
bean.setAge(25);
Pattern 2
bean.put("name", "jon");
bean.put("age", Integer.valueOf(25));
前者は一般的なJava Beanを使ったもの、後者はマッピングを使ったものです。
パラメータやDO(Database Object)を扱うのに、マッピングが使われることがあります。
今度はこれによるメリット・デメリットを考えてみましょう。
メリット
マッピングは汎用的な概念ですので、全てのBeanで共通のクラスを使用することが出来ます。
通常、HashMap(およびその派生クラス)が使用されます。
Java Beanを使うと、用途によって複数のBeanクラスを作成しなければなりません。
まぁこれも当然と言えば当然なのですが、マッピングオブジェクトは汎用的なので
例えば上の例で tel という属性が追加された場合には
bean.put("tel", "0123456789");
のようにするだけで済みます。
Java Beanを使うと、Beanクラスに tel フィールドを追加して
それ用の Getter / Setter メソッドも追加する必要があります。
デメリット
汎用的であるという事は、何でもOKという事です。
例えば上の例で
bean.put("namae", "jon");
のようにタイプミスをしてしまった場合でも、それに気付くことが出来ません。
処理側では
String name = bean.get("name");
とするので、name には null が格納されるでしょう。
上と同じ理由ですが、こちらの方が頻繁に発生します。
さらに悪いことに、大くの現場ではこのミスを防ぐために
「全ての値をString型で格納する」というとんでもないコードを平気で書いています。
これはどんな結果をもたらすでしょう?
後々、ありとあらゆるバグに悩まされる事になるのです。
全てをString型で扱うことは、絶対にオススメしません。
値に適切な型を使うことは、バグを早期に発見する最大の近道なのですから。
例えば Java Bean の場合には、setName メソッドを呼んでいる箇所というのは
簡単に確認することが出来ます。
IDEを使っていれば静的に確認できますし、
デバッグ時にもブレークポイントを置くだけで確認できます。
それに対し、マッピング方式ではこれを確認する方法がありません。
ですから、「nameにおかしな値が入ってるんだけど、一体これどこで格納されたのか」
というような問題に直面すると、解決に非常に時間が掛かることになります。
そしてこれは、実際に開発をしていると頻繁に発生する事態なのです。
僕は、これこそがマッピング最大の欠点だと考えています。
Java Bean を使っていれば、bean.set とタイプした段階で補完キー(Ctrl + Spaceなど)を
押せば、Setterメソッドの一覧がIDEによって表示されるでしょう。
それに対してマッピング形式では、name や age といった文字列を
自分で探し出してタイプしてやる必要があります。
ひどい場合、これらの文字列が参照側のコードに直接記述してあり
そこを見るしか無いといった事態にさえなり得ます。
定数化してあればまだ良いのかもしれませんが、それではマッピングのメリットとして挙げた
「Beanクラスを修正する必要が無い」という利点を消してしまう事になります。
そして一番の問題として
「定数として定義してあってもプログラマがそれを使うかどうかは判らない」
という事実があります。
つまり…
class Constants {
public static final String KEY_NAME = "name";
}
class Client {
public void func() {
Map bean = new HashMap();
bean.put(KEY_NAME, "jon");
someFunc(bean);
}
}
このようなコードは、
bean.put("name", "jon");
としても全く同じように動くのです。
そして悲しいことに、こういった事は実際に多くの現場で起こっています。
マッピングは「キーと値」の両方を保持する必要があり
Java Bean と比べてメモリ消費量はかなり増加します。
もし、このオブジェクトを10000個持つとしたら、
そのメモリ量の差は無視できない程大きくなってしまうのです。
こちらも Reflection と同じように、デメリットの方が圧倒的に多いです。
というわけで、マッピングの使用も必要最小限に抑えましょう。
これらを解決するために、非常に有効な手段があります。
それはずばり、コードの自動生成 です。
特に、マッピングを使う必要性(メリット)はこれによってほぼ無くなります。
Javaのような静的言語の弱点を補うために、やれる手段はいくらでもあるのです。
さて、以上のことを踏まえてもリフレクションやマッピングを使わざるを得ない場面もあります。
こんなとき、それによるバグの発生を最小限に抑えるために
是非やってほしいことがあります。
それは、テストの自動化です。
例えばマッピングではフィールド名が(tel_no→tel_numberのように)変更されても
そのロジックが実行されるまで不具合に気付くことができません。
どんなに優秀なIDEを使っていても、です。
テストを自動化していれば、この不具合を瞬時に見抜くことが出来ます。
テスト自動化のメリットは、こんなところにもあるのです。
自動化テストのメリット でも書いたように、自動化されたテストは
バグを未然に防ぐという目に見えない(しかしとても大きな)効能があるのです。