CamelCase と snake_case を相互変換する

  • 15
    いいね
  • 0
    コメント

背景

「Java で CamelCase(単語の区切りで大文字にする記法)と snake_case(単語の区切りで_を使って連結する記法)を相互変換するにはどうしたらよいか?」という話題が会社で出ました。Java の標準ライブラリにはこの機能がないので、意外に必要とされないのでしょうか?

CaseFormat

調べてみたところ、Google Guava にこの変換をやるためのクラス CaseFormat があることを知りました。元の文字列のフォーマットを指定して、to メソッドで変換先のフォーマットを指定する、という感じで使うようです。なお、今回私の言う snake_case は LOWER_UNDERSCORE という名前になっています。

CamelCase から snake_case への変換は下記のコードで可能です。

final String snake = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "TomatoCurry");

逆に、snake_case から CamelCase への変換は下記の通りです。

final String camel = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "tomato_curry");

簡単でわかりやすいですね。Guava はライセンスも Apache License 2.0 ですし、通常はこちらを使っておけば間違いないでしょう。


自作してみる

ただ、1機能を使いたいためだけに Guava を導入するのも何だと思ったので、自分で似た機能を勉強も兼ねて実装してみました。

ソースコード

全体はこちら

camelToSnake()
    public static final String camelToSnake(final String camel) {
        if (StringUtils.isEmpty(camel)) {
            return camel;
        }
        final StringBuilder sb = new StringBuilder(camel.length() + camel.length());
        for (int i = 0; i < camel.length(); i++) {
            final char c = camel.charAt(i);
            if (Character.isUpperCase(c)) {
                sb.append(sb.length() != 0 ? '_' : "").append(Character.toLowerCase(c));
            } else {
                sb.append(Character.toLowerCase(c));
            }
        }
        return sb.toString();
    }
snakeToCamel()
    public static final String snakeToCamel(final String snake) {
        if (StringUtils.isEmpty(snake)) {
            return snake;
        }
        final StringBuilder sb = new StringBuilder(snake.length() + snake.length());
        for (int i = 0; i < snake.length(); i++) {
            final char c = snake.charAt(i);
            if (c == '_') {
                sb.append((i + 1) < snake.length() ? Character.toUpperCase(snake.charAt(++i)) : "");
            } else {
                sb.append(sb.length() == 0 ? Character.toUpperCase(c) : Character.toLowerCase(c));
            }
        }
        return sb.toString();
    }

渡された文字列を1字1字見て、変換対象の文字列があったら処理をして StringBuilder に append し、最後に新しい文字列オブジェクトを生成して返す、という単純な処理をするメソッドです。


比較・検証

せっかくなので実行速度を計測してみることにしました。検証用のコードはこちらです。なお、Collection フレームワークには GS Collections を使いました。

Method Average time[ms]
Guava 450.2
CharAt 277.0
Array 225.6

自作の方は雑な機能しかない分だけ速く実行できているようです。


【追記】 JMH でのパフォーマンスチェック

その後、Java のコードのパフォーマンスチェックは JMH を使うべきだと知りましたので、改めて JMH でやり直しました。

修正差分

JMH を使うための修正差分は下記の commit をご覧ください。

https://github.com/toastkidjp/snake_camel/commit/2f3d677c6eac8751949b660ef640142ccf1dc121

実行環境

Key Value
Java SE 1.8.0_u121
JMH 1.18
Gradle 3.4.1
OS Windows 10
CPU Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz 2.90 GHz
RAM 16.0GB

JMH の設定

Key Value
jmhVersion 1.18
warmupIterations 5
iterations 20
fork 2
benchmarkMode ['thrpt']
failOnError true

実行結果

# Run complete. Total time: 00:02:40

Benchmark                           Mode  Cnt   Score   Error   Units
SnakeCamelBenchmark.convertGuava   thrpt   40  32.391 ± 6.586  ops/ms
SnakeCamelBenchmark.convertCharAt  thrpt   40  37.823 ± 4.994  ops/ms
SnakeCamelBenchmark.convertArray   thrpt   40  27.984 ± 2.503  ops/ms

結論

Guava の CaseFormat クラスを使いましょう。

GitHub repository