新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。もう新人じゃないかも…

Javaの学習:実用的なリフレクション(2)

[Java] Javaの学習

内容:9章 実用的なリフレクション

java.beansパッケージ
  • ビジュアルツールを使ってGUIを構築するために設計されたパッケージ
  • リフレクションで使えるクラスがたくさんある
  • java.beans.Introspectorクラス
    • java.beans命名規則に基づいて宣言されているクラスのプロパティ情報を得るために使用するシングルトンクラス
    • クラス情報の参照をキャッシュするため、リフレクション処理が非常に速い
  • java.beans.PropertyDesctiptorクラス
  • java.beans.IndexedPropertyDesctiptorクラス
    • 配列のプロパティを扱う追加機能付き
  • MethodDesctiptorクラスとParameterDesctiptorクラス
  • java.beans.BeanInfoクラス
    • 特定のJavaBeanについての全情報が含まれる

※シングルトンクラス
クラスのインスタンスを一つに保つデザインパターン

public class MySingleton {

  // このクラスに唯一のインスタンス
  private static MySingleton instance = new MySingleton();

  // privateなコンストラクタ
  private MySingleton() {}

  // インスタンス取得メソッド
  public static MySingleton getInstance() {
    return instance;
  }
                  ...
}

参考:@IT:Java TIPS -- クラスのインスタンスを1つに保つ(Singletonパターン)

toString()上でのリフレクション
  • 元々のtoStringはハッシュコードとオブジェクトの型しか表示しない
    • debugには不十分、可変オブジェクト内の全プロパティ値が見たい
  • toString()を再抽象化し、各クラスで再定義するのも一つの手
    • だが、各クラスのtoString()でやることはほぼ同じな上、プロパティの追加や削除時が面倒
  • MutableObjectクラスのtoString()をリフレクションで便利にする
public class MutableObject {
	protected final transient PropertyChangeSupport propertyChangeSupport =
			new PropertyChangeSupport(this);
	protected MutableObject() {

	}

	@Override
	public String toString() {

		try {
			// BeanInfoオブジェクトを取得
			final BeanInfo info = Introspector.getBeanInfo(this.getClass(),
					Object.class);
			// BeanInfoオブジェクトからPropertyDescriptorオブジェクトの配列を取得
			final PropertyDescriptor[] props = info.getPropertyDescriptors();
			// 出力する文字列
			final StringBuffer sb = new StringBuffer(500);
			Object value = null;
			// クラス名を追加
			sb.append(getClass().getName());
			sb.append("@");
			// ハッシュコードを追加
			sb.append(hashCode());
			sb.append("={");
			for (int i = 0; i < props.length; ++i) {
				if (i != 0) {
					sb.append(", ");
				}
				// プロパティの名前を取得
				sb.append(props[i].getName());
				sb.append("=");
				if (props[i].getReadMethod() != null) {
					// gettterメソッドを実行してプロパティの値を取得
					value = props[i].getReadMethod().invoke(this, null);
					if (value instanceof MutableObject) {
						sb.append("@");
						sb.append(value.hashCode());
					} else if (value instanceof Collection) {
						sb.append("{");
						for (Object element : (Collection) value) {
							if (element instanceof MutableObject) {
								sb.append("@");
								sb.append(element.hashCode());
							} else {
								sb.append(element.toString());
							}
						}
						sb.append("}");
					} else if (value instanceof Map) {
						sb.append("{");
						Map map = (Map) value;
						for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
							Object key = iter.next();
							Object element = map.get(key);
							sb.append(key.toString());
							sb.append("=");
							if (element instanceof MutableObject) {
								sb.append("@");
								sb.append(element.hashCode());
							} else {
								sb.append(element.toString());
							}
						}
						sb.append("}");
					} else {
						sb.append(value);
					}
				}
			}
			sb.append("}");
			return sb.toString();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

public class DemoToStringUsage {

	public static void main(String[] args) {
		Person p = new Person();

		System.out.println(p);

		p.setFirstName("Hoge");
		p.setLastName("Nyoro");
		p.setGender(Gender.MALE);
		p.setTaxID("123456ab");

		System.out.println(p);
	}
}

/*
 * 出力:
 * hcj.datamodel.Person0-639581149={birthDate=Mon Jun 09 19:02:17 JST 2014, firstName=<<NEW PERSON>>, gender=hcj.datamodel.Gender.MALE, lastName=<<NEW PERSON>>, taxID=}
 * hcj.datamodel.Person031577499={birthDate=Mon Jun 09 19:02:17 JST 2014, firstName=Hoge, gender=hcj.datamodel.Gender.MALE, lastName=Nyoro, taxID=123456ab}

 */
制約オブジェクトの取得
  • 各クラスに定義された制約オブジェクトを取得
  • パネルやJSPページでこのオブジェクトを使って制約を取得できると便利
public class MutableObject {
	// 制約オブジェクトと制約オブジェクトの持つ名前(nameフィールド)を入れたMapのキャッシュ
	private static final Map CONSTRAINT_CACHE = new HashMap<>();

	// 制約オブジェクトを入れたMapを生成
	protected static final Map buildConstraintMap(final Class dataType)
			throws IllegalAccessException {
		final int modifiers = Modifier.PUBLIC | Modifier.FINAL
				| Modifier.STATIC;

		Map constraintMap = new HashMap<>();

		final Field[] fields = dataType.getFields();
		Object value = null;
		for (int i = 0; i < fields.length; ++i) {
			if ((fields[i].getModifiers() & modifiers) == modifiers) {
				value = fields[i].get(null);
				if (value instanceof ObjectConstraint) {
					constraintMap.put(((ObjectConstraint) value).getName(),
							value);
				}
			}
		}
		return Collections.unmodifiableMap(constraintMap);
	}
	
	// 対象となるクラス名と、制約オブジェクト名(nameフィールド)を引数に制約オブジェクトを取得
	public static ObjectConstraint getConstraint(final Class dataType, final String name) {
		Map constraintMap = getConstraintMap(dataType);
		return (ObjectConstraint)constraintMap.get(name);
	}
	
	// 対象となるクラス名を引数に制約オブジェクトを格納したMapを取得
	public static final Map getConstraintMap(final Class dataType) {
		try {
			Map constraintMap = (Map)CONSTRAINT_CACHE.get(dataType);
			if (constraintMap == null) {
				constraintMap = buildConstraintMap(dataType);
				CONSTRAINT_CACHE.put(dataType, constraintMap);
				return constraintMap;
			}
			Collections.unmodifiableMap(constraintMap);
		} catch (final IllegalAccessException ex) {
			throw new RuntimeException(ex);
		}
		return null;
	}
}

public class DemoGetConstraintMapUsage {

	public static void main(String[] args) {
		Map map = MutableObject.getConstraintMap(Person.class);
		System.out.println(map);
		ObjectConstraint co = MutableObject.getConstraint(Person.class, "firstName");
		System.out.println(co);
}

/*
 * 出力:
 * {firstName=hcj.datamodel.constrains.StringConstraint@4b1210ee, lastName=hcj.datamodel.constrains.StringConstraint@4d7e1886, gender=hcj.datamodel.constrains.ObjectConstraint@3cd1a2f1, taxID=hcj.datamodel.constrains.StringConstraint@2f0e140b, birthDate=hcj.datamodel.constrains.DateConstraint@7440e464}
 * hcj.datamodel.constrains.StringConstraint@4b1210ee
 * 
 */
リフレクションの性能
  • 上の例ではキャッシュを使っていたが、キャッシュを使わない場合は?
  • リフレクションによる性能の低下は?
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class EvalReflection {
	private String value = "hoge";

	public String getValue() {
		return this.value;
	}

	public static void main(String[] args) {
		try {
			final int CALL_AMOUNT = 1000000;
			final EvalReflection er = new EvalReflection();

			// 1) リフレクションを使わずメソッド呼び出し
			long millis = System.currentTimeMillis();

			for (int i = 0; i < CALL_AMOUNT; ++i) {
				er.getValue();
			}

			System.out.println("メソッド呼び出し" + CALL_AMOUNT + "回にかかる時間は"
					+ (System.currentTimeMillis() - millis) + "msec");

			// 2) リフレクションを用いてループごとにメソッドを検索する呼び出し
			Method md = null;
			millis = System.currentTimeMillis();

			for (int i = 0; i < CALL_AMOUNT; ++i) {
				md = er.getClass().getMethod("getValue", null);
				md.invoke(er, null);
			}

			System.out.println("リフレクションを使ったメソッド呼び出し" + CALL_AMOUNT + "回にかかる時間は"
					+ (System.currentTimeMillis() - millis) + "msec");

			// 3) メソッドをキャッシュしたリフレクションによるメソッド呼び出し
			millis = System.currentTimeMillis();
			md = er.getClass().getMethod("getValue", null);

			for (int i = 0; i < CALL_AMOUNT; ++i) {
				md.invoke(er, null);
			}

			System.out.println("リフレクションを使ったメソッド呼び出し" + CALL_AMOUNT + "回にかかる時間は"
					+ (System.currentTimeMillis() - millis) + "msec");

		} catch (NoSuchMethodException | SecurityException
				| InvocationTargetException | IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}
}

/*
 * 実行結果:
 * メソッド呼び出し1000000回にかかる時間は8msec
 * リフレクションを使ったメソッド呼び出し1000000回にかかる時間は293msec
 * リフレクションを使ったメソッド呼び出し1000000回にかかる時間は16msec
 */
  • Methodオブジェクトの検索は、リフレクションで一番時間が掛かる部分
    • キャッシュを用いることで性能は格段に向上
  • リフレクションによる呼び出しは通常のメソッド呼び出しよりも時間がかかる
    • ただし、性能上のロスよりも利点が多い