[Java9] テキスト中の数値をインクリメントしつつ置換する

2018年3月4日日曜日

Java 開発

t f B! P L C

猫と一緒にガジェットライフ♪ムチャです。

仕事で「テキスト中に埋め込まれた通し番号を一律増やしたい」という状況になりました。
サクッとできなそうだったので、ちょっと調べてJava9で新しく追加されたメソッドを使って解決したので、ご紹介したいと思います。


要件

:
:
obj.setRec123("");
obj.setRec124(foo.getName());
obj.setRec125("固定文字");
if(foo.isBar()){
    obj.setRec126(foo.getParam1());
    obj.setRec127("1");
}else{
    obj.setRec126("");
    obj.setRec127(foo.getParam2());
}
:
:
こんな感じのコードがありました。クソコードですねw

objに詰めたものは最終的にCSVかなんかになるので変数自体にあまり意味はないとはいえ、もし途中に項目が追加になったら大変です。

で、10個ほど追加することになったのですが(;´∀`)
手で直してもいいですが、面倒だし間違えてもエラーになったりせずチェックが面倒です。

正規表現でサクッとできそうな感じもありますが、マッチさせて後方参照した後に計算するのは難しそうです。どうもPerlだとできるぽいのですが、サクラエディタやJavaだとできませんでした。


対応

じゃあどうするかなと。Java9のAPIドキュメント見てたら9でjava.util.regex.Matcherクラスに新しく追加になったメソッドで解決できそうでした。

それが以下です。

public String replaceAll​(Function<MatchResult,String> replacer)
元々Stringを取るreplaceAllはありましたが、java.util.function.Functionを取るものが追加されていました。
ラムダ式ですね。

このメソッドがなかなか優れもので、マッチしていなければ元の文字列をそのまま、マッチしていたらラムダ式を呼び出してマッチ部分を置換した文字列を返してくれます。

普通にやるとif文と文字列結合をあれこれしないといけないですが、1行でスッキリ書けます。


結果

というわけで、書いたコードがこちら。
//問題のテキストをファイルにしておいて読み込み
Path in = Paths.get("c:/temp", "input.txt");

//RecNNNという通し番号をヒットさせる
Pattern pt = Pattern.compile("(Rec)(\\d+)");

//ファイルを開いて1行ずつのストリームに
Files.lines(in, StandardCharsets.UTF_8)
    //マッチさせてMatcherに
    .map(s -> pt.matcher(s))
    //replaceAllにラムダ式を渡す
    .map(m -> m.replaceAll(mr -> mr.group(1) + (Integer.parseInt(mr.group(2)) + 12)))
    .forEach(System.out::println);
 
java.nio.file.Files#linesはファイルを開いて1行ずつストリームに流してくれます。
開くためにはjava.nio.file.Pathクラスじゃないとダメなので最初に取得しています。

正規表現は何かしら接頭辞がないと関係無いところまでヒットしてしまうので、合わせて()で後方参照できるようにしておきます。

普段通りのマッチ方法でStringからMatcherにマップし、次の行で置換を行っています。

該当のメソッドはjava.util.function.Functionを引数に取ります。これは引数1つ、戻り値有のapplyメソッドを持つ関数型インタフェースです。ですのでラムダ式を渡すことができます。関数型インタフェースとラムダ式については過去記事で解説してますのでご参照ください。

ラムダ式を通常通り書くと以下のようになります。
new Function<MatchResult, String>() {
        @Override
        public String apply(MatchResult mr) {
            return mr.group(1) + (Integer.parseInt(mr.group(2)) + 12);
        }
    }

ラムダ式にはjava.util.regex.MatchResultインタフェースが引数で渡されます。同パッケージのMatcherと似てますが、マッチした結果だけを扱うことができます。

groupメソッドでマッチした文字列を得られます。引数0は元の文字列で、1からが後方参照の番号になります。
今回使っている正規表現には()が2つあるので、1つ目が接頭辞に当たる「Rec」、2つ目は数値の繰り返しにマッチします。

接頭辞はそのまま、数値部分だけintにしてから増やしたい数値を足します。今回12を足しています。

メソッドから戻した文字列は元の文字列から該当箇所だけ入れ替わります。マッチしなかった行はラムダ式を呼び出さず、そのままストリームの次の処理へ行きます。

最後はそのままコンソールへ出力しています。
入力ファイルはUTF-8としているので、他の文字コードで保存していると例外になるので注意です。

【実行結果】
:
:
obj.setRec135("");
obj.setRec136(foo.getName());
obj.setRec137("固定文字");
if(foo.isBar()){
    obj.setRec138(foo.getParam1());
    obj.setRec139("");
}else{
    obj.setRec138("");
    obj.setRec139(foo.getParam2());
}
:
:
バッチリ増やせました。


おしまいのひとこと

と、い・う・わ・け・で。

仕事でもJava9が使いたいです。・゚・(ノД`)・゚・。

とわがままを言っても仕方ないので、自分で色々書いてみないとダメですね。

それではみなさまよきガジェットライフを(´∀`)ノ




▼ブログを気に入っていただけたらRSS登録をお願いします!
▼ブログランキング参加中!応援よろしくお願いします。

ブログ内検索

自己紹介

猫とガジェットが好きなJava屋さんです。うつ病で休職後退職し、1年半の休養後に社会復帰。・・・が、いろいろあって再び退職。さらに1年休職の後に復帰して、なんとかSE続けてます。茶トラのすずと一緒に生活していましたが、2014年9月4日に亡くなって1人に。

より詳細なプロフィールはこちら↓

↓更新情報を受け取るにはフォローをお願いします!

Instagramでフォロー

※ヘッダー及びアイコンで使用しているドロイド君は、googleが作成、提供しているコンテンツをベースに複製したものです。

▼ココナラでメンターサービスを販売しています。招待コード「C3VG3」で1000ポイントもらえます。
▼欲しい物リスト

QooQ