感謝のプログラミング 10000時間

たどり着いた結果(さき)は、感謝でした。

Javaのリフレクションを使ってクラスオブジェクトを生成したり、メソッドを呼び出すためのサンプル

スポンサーリンク

※追記
追加記事を書きました。
はてブでコメントをいただいたので、さっそくSpring Frameworkでリフレクションを使ってる部分を読んでみた。
※追記ここまで

リフレクションとは、実行中に型情報を取得でき、方そのものを操作対象にできる仕組みのことである。
コア・リフレクション機構であるjava.lang.reflectはロードされたクラスに関する情報へのプログラムによるアクセスを提供する。
Constructorインスタンス、Methodインスタンス、Fieldインスタンスにより、それらの実際の対象物を反射的(reflectively)に操作することができる。

定義だけ見てもよくわからんので、リフレクションを使ってインスタンスにアクセスしてみる。

package kiso;

public class ReflectionSample {
    public static void main(String[] args) {
        Class<?> clazz = null;
        try {
            clazz = Class.forName("java.lang.StringBuilder");
        } catch(ClassNotFoundException e) {
            System.err.println("そんなクラス、見つかりません!");
        }
        
        try {
            StringBuilder sb = (StringBuilder) clazz.newInstance();
            sb.append("僕の名前は");
            sb.append("ヤンボー!");
            sb.append("僕の名前は");
            sb.append("マーボー!");
            System.out.println(sb.toString() + "2人合わせてヤンマーだ!");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

こんな感じで、Class.forNameの引数に"java.lang.StringBuilder"を指定することで、StringBuilderオブジェクトへの参照を取得することができる。

clazz = Class.forName("java.lang.StringBuilder");

実行した結果は以下のようになる。

僕の名前はヤンボー!僕の名前はマーボー!2人合わせてヤンマーだ!

ヤンマー懐かしい!!!!

では、今度はメソッドの呼び出しもリフレクションでやってみる。

package kiso;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionSample {
    public static void main(String[] args) {
        Class<?> clazz = null;
        Method appendMethod = null;
        try {
            clazz = Class.forName("java.lang.StringBuilder");
            //appendメソッドと、引数にString、という意味
            appendMethod = clazz.getMethod("append", String.class);
        } catch(ClassNotFoundException e) {
            System.err.println("そんなクラス、見つかりません!");
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        //使ってみる
        try {
            StringBuilder sb = (StringBuilder) clazz.newInstance();
            appendMethod.invoke(sb, "君と僕とでヤンマーだ!");
            appendMethod.invoke(sb, "小さなものから大きなものまで動かす力だ!");
            appendMethod.invoke(sb, "ヤンマーディーゼルー!");
            System.out.println(sb.toString());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

ポイントはここ。

Method appendMethod = clazz.getMethod("append", String.class);

ここの第一引数にメソッド名。第二引数に、メソッドの引数を指定している。
これで、メソッドオブジェクトの参照を取得できる。
で、invokeでメソッドを実際に実行することができる。
実行結果はこちら。

君と僕とでヤンマーだ!小さなものから大きなものまで動かす力だ!ヤンマーディーゼルー!

JBossとかも内部でこういうの使ってるんだろうな。ソースを見てみないと。

超簡単なDIのサンプルを作ってみる

DIとは、Dependancy Injectionの略で、あるクラスが必要とする値やインスタンスDIコンテナが生成・取得し、そのクラスのインスタンスに対してセットすることである。
Depandancy(依存、依存性)というのは、あるクラスが自分の責任を果たすために必要となるフィールド値や、内部で持ってたり利用する他のクラスとの関係のことをいう。

言葉のまま、そのクラスが役割を果たすために依存するもの、と考えればいい。

Injectionというのは「注入」という意味で、あるクラスのインスタンスに対して外部から依存性をセットすることである。

外部から依存性をセットできるから、DIという。
代表的なDIコンテナを持つフレームワークに、Spring Frameworkなどがある。

従来のファクトリパターンが、個々のクラスごとにオブジェクト生成を隠蔽したのに対し、DIエンジンは依存関係のあるオブジェクト群の構築を一括して隠蔽する。

ここまでは本を参考に調べながら書いてるからいいんだけれど。


ちょっとここからはめちゃくちゃだ。
DIっぽく外側からクラスを注入するサンプルを作ってみる。

ひどいサンプルだが、外から名前を指定すればインスタンスが取れるよ、ということだけつかめれば・・・。

package kiso;

import java.lang.reflect.Constructor;

public class DISample {
    
    public static Object classInjection(String targetType) throws Exception {
        Object diObject = null;
        Class<?> targetClass = Class.forName(targetType);
        if (targetClass == null) {
            System.out.println("指定しているクラス名がたぶんおかしいよ!");
        } else {
            diObject = targetClass.newInstance();
        }
        return diObject;
    }
    
    public static void main(String[] args) throws Exception {
        
        DISample myDi = new DISample();
        StringBuilder sb = (StringBuilder) myDi.classInjection("java.lang.StringBuilder");
        sb.append("Yahoo!");
        System.out.println(sb.toString());
    }
}

これは、mainメソッドの中で
classInjection("java.lang.StringBuilder")を呼び出すと、
classInjectionのメソッドの中でこのインスタンスを作って返してくれる、というサンプル。
実行すると

Yahoo!

と表示される。StringBuilderインスタンスを作ることができた。

おかしなサンプルになってしまったけれど、
たとえばこの"java.lang.StringBuilder"をXMLに書いて、そのXMLを読み込んで、インスタンスを生成する、なんて使い方もできるはず。

ちなみに、パーフェクトJavaにはもっとちゃんとしたサンプルが載っている。

応用できないということは、ちゃんと理解できていないということだ。
なので、これからもっと深掘りして勉強する。

その他

・System.exitはJVM全体を終了させるため、呼ばれるのが適切であることはほとんどない。しかし、コマンドラインのユーティリティの異常終了に対しては適切である。
(「Effective Java」p.225より)

参考にした本

パーフェクトJava (PERFECT SERIES) (PERFECT SERIES 2)

パーフェクトJava (PERFECT SERIES) (PERFECT SERIES 2)

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

入門系の本だとリフレクションの説明をしてくれるものはあまりないので、ここまでちゃんと解説してくれるのは非常に助かる。
Effective Javaは紙面の構成が読みづらくて苦手だったけど、書いてあることは素晴らしいので、毛嫌いせずに読んでいきたい。