Stream の reduceで文字列を長さで分割 - Oboe吹きプログラマの黙示録
を書いた時、応用すれば長いコードを書かなくても済むのでは?と思いました。
解析対象の固定長フォーマットの文字列、長さ 4,2,6,14 の文字列長の連結
String str = "uranA1 98120230312114807";
これを以下、文字列を解析して取得するJava Object のクラスになるとする。
import java.time.LocalDateTime; import lombok.Data; @Data public class Foo { private String name; private String rank; private int value; private LocalDateTime atime; }
文字列String を substring で取得する Function<T, R> と成果物に対する BiConsumer<T, U> が
成立すれば良いのです。
以前、yipuan-core ライブラリとして、ApplyConsumer という名の関数型インターフェース
https://github.com/yipuran/yipuran-core/wiki#applyconsumert-u
を作っています。
ソースは、、、
https://github.com/yipuran/yipuran-core/blob/master/src/main/java/org/yipuran/function/ApplyConsumer.java
これを使って、以下、列挙型で解析対象のクラスへの変換定義として書きます。
public enum FooConfig{ NAME(1, 4, ApplyConsumer.of(s->s, Foo::setName)), RANK(2, 2, ApplyConsumer.of(s->s, Foo::setRank)), VALUE(3, 6, ApplyConsumer.of(s->Integer.parseInt(s.trim()), Foo::setValue)), ATIME(4, 14, ApplyConsumer.of( s->LocalDateTime.parse(s, DateTimeFormatter.ofPattern("uuuuMMddHHmmss") .withResolverStyle(ResolverStyle.STRICT)), Foo::setAtime)); private int order; private int len; private ApplyConsumer<String, Foo> ap; private FooConfig(int order, int len, ApplyConsumer<String, Foo> ap) { this.order = order; this.len = len; this.ap = ap; } public ApplyConsumer<String, Foo> ApplyConsumer(){ return ap; } public int getLen() { return len; } public Integer getOrder() { return order; } public static List<FooConfig> list(){ return Arrays.stream(FooConfig.values()) .sorted((a, b)->a.getOrder().compareTo(b.getOrder())) .collect(Collectors.toList()); } }
この列挙型は、
int order; で書式文字列の中の並びの順番を定義してます。
int len; で、各フィールドの文字列長を定義してます。
ApplyConsumer で指定する Function<T, R> も、ここで String の trim() を書いたり、
場合によっては、例外を発生させたり用途に応じた変換を書けるわけです。
使用例は、以下のように書けます。
FooConfig.list() で書式の並び順のFooConfigリストから、String#substring で分割した結果を
一時保存してから、ApplyConsumer を実行する
List<String> datalist = new ArrayList<>(); FooConfig.list().stream().map(e->e.getLen()) .reduce(0, (a, b)->{ datalist.add(str.substring(a, (a+b))); return a+b; }); Foo foo = FooConfig.list().stream().collect(Foo::new,(r, t)->{ t.getApplyConsumer().accept(datalist.get(t.getOrder()-1), r); },(r, t)->{});
String#substring で分割した結果の一時保存をしない場合は、以下のように
reduce の実行だけで済ませる。その代わり求めるオブジェクトはあらかじめ生成しておく。
Foo foo = new Foo(); List<FooConfig> configlist = FooConfig.list(); AtomicInteger count = new AtomicInteger(0); configlist.stream().map(e->e.getLen()) .reduce(0, (a, b)->{ FooConfig config = configlist.get(count.getAndIncrement()); config.getApplyConsumer().accept(str.substring(a, (a+b)), foo); return a+b; });
生成した Foo を標準出力すると、、lombok の toString() で
Foo(name=uran, rank=A1, value=981, atime=2023-03-12T11:48:07)
となります。