No Programming, No Life

プログラミング関連の話題や雑記

JavaでPropertyDescriptorを使ってBeanのプロパティに簡単にアクセスする

はじめに

Java標準のSDKjava.beans パッケージがありますが、この中にJavaBeansの getter/setter を便利に扱える PropertyDescriptor というクラスがあるようです。

クラス階層

java.beansClass PropertyDescriptorjava.lang.Object
 └java.beans.FeatureDescriptor
  └java.beans.PropertyDescriptor

使い方

それでは早速使い方を見て行きましょう

1. よくあるBeanをとりあえず定義
class User {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

こんな感じですよね、とりあえず、nameという名前のString型フィールドだけ持っているUserというBeanを考えてみましょう。

2. まずはメソッド(プロパティ)を指定してnewする

さて、早速PropertyDescriptorを使っていって見ましょう。いくつかコンストラクタはありますが、とりあえず今回はこんな感じで使ってみましょう。

PropertyDescriptor nameProp = new PropertyDescriptor("name", User.class);

コンストラクタの第一引数に getter/setter として定義されているメソッドの get/set を抜いた部分(プロパティ名)を指定して、第二引数に取得したいBeanのClassクラスを指定します。
上記のコードは nameProp という変数名で name のプロパティを取得したイメージです。

また、第二引数はClassクラスなので、上記例では、Userのstaticフィールド class で取得しましたが、直接インスタンスから取得してしまっても構いません。

User userBean = new User();
PropertyDescriptor nameProp = new PropertyDescriptor("name", userBean.getClass());

こんな感じですね。

3. Methodを取得する

さて、では次に作成した PropertyDescriptor からMethodを取得してみましょう。

まずはゲッターを取得してみましょう。ゲッターを取得するには #getReadMethod というメソッドを利用します。そう、ゲッターは読み込むのためのメソッドですよね。戻り値は java.lang.reflect.Method になります。

Method nameGetter = nameProp.getReadMethod();

次はセッターを取得してみましょう。勘のイイ方はお気づきですよね?そうセッターは #getWriteMethod で取得できます。セッターは書き込むためのメソッドですので。戻り値はゲッターと同様 java.lang.reflect.Method になります。

Method nameSetter = nameProp.getWriteMethod();

つまり、name というプロパティ名を指定して作った PropertyDescriptor(nameProp) から、ゲッターのメソッド(nameGetter) と セッターのメソッド(nameSetter) の両方が取得できたことになります。

4. ゲットやセットしてみる

さて、メソッドが取得できたので、ゲットやセットをしてみましょう。まずはゲットから。
呼び出しは Methodクラスの #invoke を利用します。#invokeは第一引数はそのメソッドをインヴォークしたいインスタンスを、第二引数はそのメソッドに渡したい引数をオブジェクト配列で渡すことになっています。

String name = (String) nameGetter.invoke(userBean, (Object[]) null);

注意として、ゲッターは引数がないので、nullを引数として渡すことになるんですが、nullだと引数の型が解決できなので、明示的に Object[] にキャストしてあげる必要があります。あと戻り値もObject型になってしまっているので、キャストしてあげましょう。結果はちゃんと文字列のインスタンスなんですが、見せかけ上の型がObjectになってしまうという話です。これは#invokeの第二引数は実際は具体的な型なんだけどもObject配列で渡しているのと同じ理由ですね。リフレクションではどんな型でも扱えるよう、インスタンスはObject型で操作するという。
ちょっと手がかかりますが、Javaのお茶目な一面ですね。

さて、ちと脇道に逸れましたが次はセッターに行きましょう。基本的にはゲッターと同じです。

nameSetter.invoke(userBean, "fumo");

第二引数は可変長引数 Object... になっていますので、わざわざ

nameSetter.invoke(userBean, new Object[]{ "fumo" });

とかって書かなくても平気ですよ! 便利な世の中ですね。

7. copyPropertyしてみる

では最後にプロパティのコピーをしてみましょう。ゲッターとセッターを組み合わせるだけで簡単に実現できますね、こんな感じです。

User user1 = new User();
User user2 = new User();

nameSetter.invoke(user2, nameGetter.invoke(user1, (Object[]) null));

nameSetter の #invoke の引数に nameGetter の #invoke した結果を直接入れるといった操作をここでは行っています。

8. ちなみに普通にリフレクションで書いたらどうなるか

普通にリフレクション版

try {
  Method nameGetter = User.class.getMethod("getName", (Class<?>[]) null);
  Method nameSetter = User.class.getMethod("setName", String.class);

} catch (SecurityException e) {
  e.printStackTrace();
} catch (NoSuchMethodException e) {
  e.printStackTrace();
} catch (IllegalArgumentException e) {
  e.printStackTrace();
} catch (IllegalAccessException e) {
  e.printStackTrace();
} catch (InvocationTargetException e) {
  e.printStackTrace();
}

↓↓↓
PropertyDescriptor版

PropertyDescriptor nameProp = new PropertyDescriptor("name", User.class);
Method nameGetter = nameProp.getReadMethod();
Method nameSetter = nameProp.getWriteMethod();

そうか、get/setのメソッド名の変換とかだけじゃなくて、例外処理とかもやってくれてるんだね。

おわりに

このクラスを知っていると、リフレクションのコードを書く際にちょっとだけ楽できますね。Java標準のSDKでもまだまだ知らないことがいっぱいあるなぁ。

テストで書いていたソースも載せておきます。