無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Genericsを使ったJavaBean

JavaBeanを作る時にGetter,Setterを書くのメンドウですよね。Rubyみたいに

attr :foo

とかでアクセサを作ってくれれば良いけどあいにくJavaにそんな機能はありません。(Java SE 7でできるらしいけど。)楽する手段としてMapを使うという手がありますが、これはこれで以下の問題があってほとんど使われていない(と思う)。

  • 取り得る値の型チェックが効かない。
  • 指定できるキーがどれかわからない。関係ないモデルのキーが指定できてしまう。

でも、Java5から使えるようになったGeneicsを使えば、上の問題をクリアしつつMapをBeanに仕立て上げることができるんじゃね、と思って作ってみました。

戦略

ネタ的には タイプセーフなPropertiesを作る。に近いです。

  • API
    • Map風なAPIとしてアクセサを提供する。
      • 「get(<キー>)」で属性返す。
      • 「set(<キー>、<値>)」で属性を設定。
  • プロパティを取得するためのキー
    • 値の型はプロパティごとに決まるので、プロパティの型パラメータで値の型を指定できるようにする。
    • 別の型パラメータで「このプロパティを持つモデルの型」を指定できるようにする。
  • モデル(Bean)のAPI
    • setでは、プロパティの型パラメータにあった値しか指定できないように制限する。
    • getで返す値も、プロパティの型パラメータにあったものを返すようにする。
    • また、モデルクラス自体を型パラメータに持つPropertyしかキーとして受け付けないようにし、関係ないモデルでプロパティが使われることを防ぐ。

実装

まずはプロパティ。列挙型には型パラメータが渡せないので独自に作ります。

/**
 * プロパティのキー
 *
 * @param <M> プロパティを持つモデルの型
 * @param <T> プロパティが取り得るデータの型
 *
 * @version $Revision:$
 * @author  $Author:$
 */
public class Property<M, T> {

    /**キー*/
    private final String key;

    /**
     * コンストラクタ
     * @param key キー
     */
    protected Property( String key ) {
        this.key = key;
    }

    /* 継承元のクラスのJavaDocを参照 */
    public boolean equals ( Object obj ) {
        if ( obj == null ) { return false; }
        if ( obj instanceof Property) {
            Property that = (Property) obj;
            return that.key == null ? key == null : that.key.equals( key );
        }
        return false;
    }
    /* 継承元のクラスのJavaDocを参照 */
    public int hashCode () {
        return key.hashCode();
    }
}

Beanは抽象クラスの「AbstractBean」を継承して作ります。「AbstractBean」はMapと、プロパティを受け取って値を返す汎用的なアクセサを持ちます。

import java.util.HashMap;
import java.util.Map;

/**
 * Beanの抽象基底クラス。
 *
 * @version $Revision:$
 * @author  $Author:$
 *
 * @param <M> Beanの型
 */
public abstract class AbstractBean<M> {

    /***
     * 値の格納先
     */
    private Map<Property<M, ?>, Object> props =
        new HashMap<Property<M, ?>, Object>();

    /**
     * 値を取得する。
     * @param <V> 値の型
     * @param key プロパティキー
     * @return
     */
    public <V> V get( Property<M, V> key ) {
        return (V) props.get( key );
    }

    /**
     * 値を設定する。
     * @param <V> 値の型
     * @param key プロパティキー
     * @param value
     */
    public <V> void set( Property<M, V> key, V value ) {
        props.put( key, value );
    }
}

最後はBeanの実体です。定数でプロパティ定義を持ちます。

/**
 * Kitten.
 *
 * @version $Revision:$
 * @author  $Author:$
 */
public final class Kitten extends AbstractBean<Kitten> {

    /**
     * Kittenのプロパティ
     * @param <V> プロパティが取り得る値の型
     */
    private static final class KittenProperty<V>
    extends Property<Kitten, V> {
        /**
         * コンストラクタ
         * @param key キー
         */
        protected KittenProperty ( String key ) {
            super( key );
        }
    }

    /**プロパティ:名前*/
    public static final KittenProperty<String> NAME
        = new KittenProperty<String>( "NAME" );

    /**プロパティ:年齢*/
    public static final KittenProperty<Integer> AGE
        = new KittenProperty<Integer>( "AGE" );
}

テスト用にもう1つ。

/**
 * Penguin.
 */
public final class Penguin extends AbstractBean<Penguin> {
    /**
     * Penguinのプロパティ
     * @param <V> プロパティが取り得る値の型
     */
    private static final class PenguinProperty<V>
    extends Property<Penguin, V> {
        /**
         * コンストラクタ
         * @param key キー
         */
        protected PenguinProperty ( String key ) {
            super( key );
        }
    }
    /**プロパティ:名前*/
    public static final PenguinProperty<String> NAME
        = new PenguinProperty<String>( "NAME" );

    /**プロパティ:年齢*/
    public static final PenguinProperty<Integer> AGE
        = new PenguinProperty<Integer>( "AGE" );
}

これだけ。以下のような感じで使えます。

Kitten mii = new Kitten();

// get, setでの値の型チェックが効く。
mii.set( Kitten.NAME, "mii" );
mii.set( Kitten.AGE, 1 );
//mii.set( Kitten.NAME, 1 ); // コンパイルエラー
//mii.set( Kitten.AGE, "" ); // コンパイルエラー

String name = mii.get( Kitten.NAME );
int age = mii.get( Kitten.AGE );
//int age2 = mii.get( Kitten.NAME ); // コンパイルエラー
//String name2 = mii.get( Kitten.AGE ); // コンパイルエラー

// 他のモデルクラス用のキーも指定できない。
//mii.set( Penguin.NAME, "penny" ); // コンパイルエラー
//mii.set( Penguin.AGE,  1 ); // コンパイルエラー

メリット

Map likeなAPIですが、指定可能なキーの型チェックも、値の型チェックも効きます。プロパティの追加は定数を追加するだけでOK。フィールドを足してGetメソッド書いて・・とかしなくて済みます。あと、コードも短くなる!

既知の問題

ただし、以下のような問題はあったりします。

// 次のようなプロパティが作られた場合はエラーにならない・・・。
// 有効なKittenのプロパティとして認識される。
class FooProperty<V> extends Property<Kitten, V> {
    protected FooProperty ( String key ) {
        super( key );
    }
}
FooProperty<String> foo = new FooProperty<String>("foo");
String str = mii.get( foo );
mii.set( foo, "hoge" );

まぁ、こういう使い方はするな、ということで運用で回避すべし。