JavaBeansで作成されたオブジェクトをメソッドチェーンでどう扱うかについて考えました。
JavaBeansの仕様
正確な仕様かどうかはわかりませんが、よくあるJavaBeansの仕様は以下のようなものですね。
- 引き数なしのコンストラクタを持つ
- プロパティを持つ(setter/getter)
ほかにもいろいろありますが、大事なのはこんなもん。
公式の仕様はこちら→JavaBeans Spec
public class HogeBean {
private String name;
private String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
こんな感じ。lombokを使えば
@Data
public class HogeBean {
private String name;
private String id;
}
これでOK。
メソッドチェーンでの問題点
例えば似たようなフィールドを持つHogeBeanとFugaBeanがあるとする。
@Data
public class HogeBean {
private String name;
private String id;
}
@Data
public class FugaBean {
private String id;
private String password;
}
Stream中でHogeBeanをFugaBeanに変換したい場合、普通に考えると
List<HogeBean> hoges = ...
hoges.stream().map(hoge -> {
FugaBean fuga = new FugaBean();
fuga.setId(hoge.getId());
return fuga;
}).終端処理
こんな感じでしか書くことになる。こんなの美しくない。
解決策1 匿名クラス + インスタンスイニシャライザ
あまり美しいとは言えないかもしれませんが、map内の中括弧 + return は防げるパターン。
hoges.stream().map(hoge -> new FugaBean(){
{
setId(hoge.getId());
}
}).終端処理
さっきのよりはだいぶスッキリ。
ただ欠点があって、
- 匿名クラスを使うため、厳密にはそのクラスではない。→ リフレクションとかでバグる可能性がある
- そもそも対象のクラスがfinalだと使えない
解決策2 setterで自身を返すようにして、1行でオブジェクト生成可能なようにする。
JavaBeansをやめるという選択肢。
setterを以下のように改造する。
public HogeBean setId(String id) {
this.id = id;
return this;
}
lombokならAccessors(chain = true)を付けるだけ
@Data
@Accessors(chain = true)
件の処理は下記のように出来て
hoges.stream().map(hoge -> new FugaBean().setId(hoge.getId())).終端処理
スッキリ。
でもやっぱり欠点はあって、
- JavaBeans仕様から外れるため、いろんなものが使えなくなる(PropertyDescriptor, apacheのBeanUtils, フレームワーク各種)
解決策3 ちょっとしたBuilderを作る
こんなん
public final class BeanCreater<T> {
private BeanCreater() {
}
private T bean;
public static <T> BeanCreater<T> of(Supplier<T> supplier) {
BeanCreater<T> builder = new BeanCreater<T>();
builder.bean = supplier.get();
return builder;
}
public BeanCreater<T> construct(Consumer<T> consumer) {
consumer.accept(this.bean);
return this;
}
public T build() {
return this.bean;
}
}
これを使えば、
hoges.stream().map(hoge -> BeanCreater.of(FugaBean::new)
.construct(bean -> bean.setId(hoge.getId())).build()).終端処理
うーん。やってみたものの、いまいち。コードにノイズが多すぎますね。
結論
個人的には解決策2が一番好きです。流れるようなインタフェースをかけるし、本質と儀式的な話でも無駄がない。
JavaBeansが現状を考えてもっと柔軟になってほしいなあと思います。(そんな訳にもいかないんでしょうが... )
今回作成したBeanCreaterはこちら https://github.com/7tsuno/BeanCreater
何か他に案があればアドバイスください!