Tuesday, December 28, 2010

Groovyによる汎用toString()

“${class-name}(${property-name}:${property-value}, …)”という形式で
クラスとプロパティ情報を返す汎用的なtoString()を実装したいとします。


まず比較対象としてのJavaでの実装例をあげます。


Java版:
----
class AnObject {
    // property definitions


    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName()).append("(");
        try {
            PropertyDescriptor[] pds =
                Introspector.getBeanInfo(this.getClass()).getPropertyDescriptors();
            for (int i = 0, c = 0, sz = pds.length; i < sz; i++) {
                PropertyDescriptor pd = pds[i];
                if (!"class".equals(pd.getDisplayName())) {
                    Object v = pd.getReadMethod().invoke(this);
                    if (c != 0) {
                        sb.append(", ");
                    }
                    sb.append(pd.getDisplayName()).append(":").append(v);
                    c++;
                }
            }
        } catch (Exception e) {}
        sb.append(")");
        return sb.toString();
    }
}
----
可読性を失わない程度に短くしていますが、それでも800回程度キーを叩かなくてはなりません。


これをGroovyで書き直します。


Groovy(java.beans利用)版:
----
class AnObject {
    // property definitions 


    String toString() {
        """${ this.class.name }(${
           try {
               Introspector.getBeanInfo(this.class).propertyDescriptors
                   .findAll{ pd ->
                       "class" != pd.displayName && "metaClass" != pd.displayName }
                   .collect{ pd ->
                       "${ pd.displayName }:${ pd.readMethod.invoke(this) }" }
                   .join(", ")
              } catch(e) {}
         })"""
    }
}
----
ずいぶん短くなりました。


MetaClassを使えばもう少し短く書けます。


Groovy(MetaClass利用)版
----
class AnObject {
    // property definitions 


    String toString() {
        """${ this.class.name }(${
            properties
                .findAll{ p -> "metaClass" != p.key && "class" != p.key }
                .collect{ p -> "${ p.key }:${ p.value }" }
                .join(", ")
        })"""
    }
}
----
※ MetaClassはプロパティのメタ情報をハッシュマップで持っている為定義順が維持されません。


実は独自の形式が不要ならばdump()メソッドを使うことでたった1行にもできます。


Groovy (dump()メソッド利用)版:
----

class AnObject {
    // property definitions 

    String toString() {
        dump()
    }
}
----
この場合、形式は"<${class-name}@${hash-code} ${property-name}=${property-value} ...>"となります。


MetaClassとdump()メソッドはどちらもJDKを拡張するGDKによって提供されます。