実行するクラスやメソッド名をURLなどのパラメータから取得して動的に制御したい。
こんにちは。技術チームの岩谷です。本連載ではオブジェクト指向プログラミング(OOP)をおさらいする連載を書かせていただいています。今回は第七回として「リフレクション」について説明します。実は私自身過去に「リフレクションとはなんですか?」と質問されたことはありません。みなさんからいただく質問は「StrutsやJerseyなどのフレームワークはURLに指定されたクラスを動的に実行していますよね?あれって中で何をしているんですか?」という質問が多いです。それに対して私は「あれはリフレクションという技法を使っているのよ」とよく答えています。
プログラム内の構造を「文字列」から指定する
今回の説明もJavaのサンプルソースを用意させていただきました。今回はソースを2本使って説明をさせてください。
まず1本目のソースです。このソースは「動的に実行されるクラス」です。このクラスにはリフレクションの要素は無いので、さらっと見ていただくだけで結構です。でも、このソースの「パッケージ名・クラス名・メソッド名」だけは覚えておいてください。
1 2 3 4 5 6 7 8 9 |
package jp.gixo.sample; public class HelloWorld { public void sayHello() { System.out.println("Hello!"); } } |
続いて2本目のソースです。このソースは「リフレクションを使用して処理を動的に実行するクラス」です。リフレクションの要素はこのソースに詰まっています。
1 2 3 4 5 6 |
String strClassName = "jp.gixo.sample.HelloWorld"; String strMethodName = "sayHello"; //リフレクションの処理 Class cl = Class.forName(strClassName); Method method = cl.getMethod(strMethodName); method.invoke(cl.newInstance()); |
この処理の実行結果は以下のようになります:
>Hello!
2本目のソースの1行目、2行目でクラス名とメソッド名を指定しています。これは文字列であり動的に値を変更することが可能です。多くのフレームワークはこの部分の文字列をURLを元にして取得する処理を行っています。(最近のフレームワークはこのプロセスにアノテーションを経由させるパターンが流行りですが、リフレクションの説明には重要ではありません)
ここからがリフレクションです。4行目のソース「指定された文字列」から「それに該当するクラスをオブジェクトとして取得」しています。そして5行目のソースでこのオブジェクトに対して「指定された文字列」から「それに該当するメソッドをオブジェクトとして取得」する処理を行っています。最後に6行目で「4行目で作ったオブジェクトをインスタンス化」してそのインスタンス内における「6行目で指定したメソッド」を実行しています。この結果、1本目のソースに記述されている処理が実行されるわけです。
「プログラム」も「データ」なのです
ここで、リフレクションの言葉の意味をウィキペディアから引用させていただきます。
情報工学においてリフレクション (reflection) とは、プログラムの実行過程でプログラム自身の構造を読み取ったり書き換えたりする技術のことを指す。
出典:ウィキペディア
ここまでの記事をお読みくださったみなさんには、この言葉の意味がおわかりいただけるのではないでしょうか。実はいつも我々が開発しているプログラムそのものも、見方を変えれば「データ」にすぎないのです。プログラムのソースコードも記述の規則がコンパイラによって定められています。この規則にしたがってプログラムそのものを一個の「データ」とみなしてアクセスを行う技法、これがリフレクションなのです。上記のサンプルソースでいえば4,5,6行目で「データとしてのプログラム」からの「データの取得」というアクセスが行われ、加えて6行目では「メソッドの実行命令発行」というアクセスが行われているわけです。
追伸:おそらくみなさんもリフレクションを使っています
JavaやRubyのご経験のある皆さんは、ご自身の作成したプログラムをコマンドから実行したことがあると思いますが、この時のコマンドは「java XXX」という感じの文字列ではなかったでしょうか?(XXXはみなさんが作成したクラス名) ご存じだと思いますが、最初の「java」は、みなさんのPCにインストールされているJavaの基本モジュール「java.exe」の事です。とすると次のXXXは、java.exeの引数ということになります。まさにこれが「プログラムも一個のデータにすぎない」ことを如実に物語っていて、java.exeは引数で指定されたXXXのクラスを入力データとしてXXXの構造を読み取り、中身に記述された処理を実行しているのです。この処理はまさにリフレクションそのものと言えるでしょう。Javaのコマンドをご自身で実行されたことのある皆さんは、実はすでにリフレクションをお使いになっているのです。
追追伸:リフレクションには注意点もあります。
文字列で指定されたクラスのメソッドの実行などを可能にするリフレクションはプログラムの自由度を向上させる便利な技法です。でもいい事ばかりではありません。それは「リフレクションの処理を実装したプログラム作成者は、安全な処理の実現に関してより重い責任が求められる」という事です。ありていにいうと「より厳密なチェック機構を自分で作らなければならない」という事です。例えば上記2本目のソース1行目を”jp.gixo.sample.HelloWorldAAA”と変更したらどうなるでしょうか?HelloWorldAAAというクラスは存在しないので実行時エラーが発生することでしょう。これは安全な処理とは言えません。リフレクションを用いないプログラムであればこの「クラスの存在チェック」はコンパイラがあらかじめ行ってくれます。「リフレクションを用いたプログラムを作成する」ということは、この「コンパイラによる安全性検査の恩恵」を捨てて、この部分の安全性に関する責任を作成者本人が負うという事を意味しているのです。
【本連載について】
- OOPと非OOPの違い|オブジェクト指向プログラミング(OOP)をおさらいしよう(1)
- 継承によってコードを再利用する|オブジェクト指向プログラミング(OOP)をおさらいしよう(2)
- インターフェースとは?~継承とは役割が違う~|オブジェクト指向プログラミング(OOP)をおさらいしよう(3)
- カプセル化とは?~安全なシステムを作る為の配慮~|オブジェクト指向プログラミング(OOP)をおさらいしよう(4)
- 静的型付けと動的型付け~JavaとJavaScriptのJSON処理を比較~|オブジェクト指向プログラミング(OOP)をおさらいしよう(5)
- ポリモルフィズム(多態性)とは?~クラスは違えど同じ目的には同じメソッド名を~|オブジェクト指向プログラミング(OOP)をおさらいしよう(6)
- リフレクションとは?~文字列で指定されたクラスのメソッドを実行~|オブジェクト指向プログラミング(OOP)をおさらいしよう(7)