Bits of Java (トップ)

VM   JDK と JRE
Language   オブジェクトとインスタンス   this と super   static   フィールドアクセス
IO   Serializable の実装
Swing   MetalLookAndFeel   イメージパネル
JavaBeans   プロパティ名について   XMLEncoder で保存
その他   正規表現テストアプリ   秀丸の強調表示   Ant のインストール   文字セット変換 Ant タスク   XAMPP + Tomcat

JavaBeans のプロパティ名

JavaBeans のプロパティはフィールドと混同されている事が多いと思います。 例えば下の SampleBean を例にすると

public class SampleBean implements Serializable {
    private String foo;
    public void setFoo(String foo) {
        this.foo = foo;
    }
    public String getFoo() {
        return foo;
    }
}
フィールド foo の存在が必須でそれから getter, setter メソッドを作成することで String 型の foo というプロパティを持つ JavaBeans になるという具合です。

実際にはプロパティはメソッド名で決まります。 setXXX getXXX のように set, get から始まるメソッド名があるとそれに続く文字列からプロパティ名が決定されます (boolean 型を戻り値とする isXXX メソッドもあります)。 上の SampleBean の場合には String 型が引数となる setFoo というメソッドがあるので foo という String 型のプロパティの存在が決定し、戻り値が String 型の getFoo メソッドもあることから foo プロパティは read-write プロパティとなります。

プロパティの取得と設定はアクセッサメソッド (getter setter) を通して行われ、 getter メソッドがプロパティの取得を行い setter メソッドがプロパティの設定を行うのであれば、メソッドの内部実装は自由です。 上の SampleBean の String 型のフィールドの foo はその変数名が bar でも hoge でも JavaBeans としてまったく問題ありませんし、

public class SampleBean implements Serializable {
    private static final String FOO_KEY = "foo";
    private java.util.HashMap map = new java.util.HashMap();
    public void setFoo(String foo) {
        map.put(FOO_KEY, foo);
    }
    public String getFoo() {
        return (String)map.get(FOO_KEY);
    }
}
このような実装でも foo という String 型の read-write プロパティを持っている JavaBeans となります。

しかし大抵の場合、プロパティへのアクセッサメソッドの実装は最初に示した SampleBean のようにプロパティの型とおなじ型のフィールドを用意してプロパティの値を設定、取得する場合がほとんどだと思います。 その場合にわざわざプロパティ名とフィールド名を別にしても混乱するだけでメリットは少ないと思われます。 よって JavaBeans のプロパティ名とフィールド名は同じになる場合がほとんどではないでしょうか。 ところがプロパティ名を決定する規則には例外があり、それによりビーンクラスの作成者はプロパティ名とフィールド名が同じだと思いこんでいるものの、実際には違っているという状況が生まれる場合があります。

通常プロパティ名はメソッド名の set, get, is (戻り値が boolean 型の場合のみ) に続く文字列の1文字目を小文字に変換した文字列になります。 例えば setFoo は Foo の1文字目を小文字に変換してプロパティ名は foo になります。 setF も F を小文字に変換してプロパティ名は f になります。 ところが setFF のプロパティ名は小文字への変換はなく FF となります。 この動作は JavaBeans の仕様 (The JavaBeans 1.01 specification の 8.8 Capitalization of inferred names) によるもので それによるとメソッド名から get, set (is) を除いた文字列の最初の2文字を調べて両方大文字の場合はそのままプロパティ名となり、そうではない場合や1文字の 場合は最初の文字を小文字に変換した文字列がプロパティ名になります。

プロパティ名を間違って認識している為にエラーが発生する可能性はウェブアプリケーションに多いと思います。 何らかの状態を運搬あるいはキープする為にセッションなどに JavaBeans を保持させるなど、ウェブアプリケーションにおいて JavaBeans を使用する場面は多々あると思います。 またウェブアプリケーションを作成する為のフレームワークやツールなどにおいて、それらの JavaBeans のプロパティを取得、設定する為に設定ファイルなどにプロパティ名を記述する場合があるかもしれません。 このような場合に間違ったプロパティ名を指定した為にエラーが発生してもプロパティ名自体は正しいと思いこんでいるのでエラーの原因の発見に時間がかかるかもしれません。

たとえ Bean クラスの作成者が get, set (is) を除いた文字列の2文字目が大文字の場合にはプロパティ名として1文字目の小文字変換が行われないことを知っていても、その Bean はそのような規則を知らない他の人が使用する場合もあるかもしれません。 よって JavaBeans を作成する場合にフィールドを使ってプロパティの状態を表す場合には特に理由が無い限りフィールド名の2文字目は小文字の方が無難と言えると思います。

下のプログラムはメソッド名とプロパティ名の関係をテストしています。 class というプロパティは Object クラスで定義されている getClass メソッドによるものです。 以外だったのは set や get の次の文字が小文字の場合でもプロパティとして認識される事とプロパティの型が違う getter メソッドと setter メソッドの両方を用意した場合にも例外等は発生せずに片方のみがプロパティとして認識されたことです。

BeanTest の実行結果は以下です。

name(AAA) type(int) write(setAAA)
name(BB) type(boolean) read(isBB)
name(IValue) type(int) read(getIValue) write(setIValue)
name(a) type(int) write(seta)
name(b) type(boolean) read(isb)
name(bar) type(int) read(getBar)
name(class) type(java.lang.Class) read(getClass)
name(foo) type(java.lang.String) read(getFoo) write(setFoo)



/*************************** SampleBean.java ***************************/
public class SampleBean implements Serializable {
    private String foo;
    private int iValue;

    public void setFoo(String foo) {
        this.foo = foo;
    }
    public String getFoo() {
        return foo;
    }

    public void setIValue(int iValue) {
        this.iValue = iValue;
    }
    public int getIValue() {
        return iValue;
    }

    public int getBar() {
        return 0;
    }
    public void setBar(String bar) {}

    public void seta(int i) {}
    public void setAAA(int i) {}
    public boolean isb() {
        return false;
    }
    public boolean isBB() {
        return false;
    }
}

/*************************** BeanTest.java ***************************/
import java.beans.*;
import java.lang.reflect.Method;

public class BeanTest {
    public static void main(String args[]) throws IntrospectionException {
        //SampleBeanクラスのJavaBeansとしての情報を保持するBeanInfoの取得
        BeanInfo info = Introspector.getBeanInfo(SampleBean.class);

        //プロパティを表現するPropertyDescriptorオブジェクトの
        //取得とプロパティ情報の表示
        PropertyDescriptor[] pds = info.getPropertyDescriptors();
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < pds.length; i++) {
            buf.setLength(0);

            buf.append("name(").append(pds[i].getName());
            buf.append(") type(").append(pds[i].getPropertyType().getName());

            Method method = pds[i].getReadMethod();
            if (method != null) {
                buf.append(") read(").append(method.getName());
            }

            method = pds[i].getWriteMethod();
            if (method != null) {
                buf.append(") write(").append(method.getName());
            }

            buf.append(")");
            System.out.println(buf);
        }
    }
}