CamelCase と snake_case を相互変換する

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

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

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

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

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

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

簡単でわかりやすいですね。Guava はライセンスも [Apache License 2.0](https://github.com/google/guava/blob/master/COPYING) ですし、通常はこちらを使っておけば間違いないでしょう。

----

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

## ソースコード
全体は[こちら](https://github.com/toastkidjp/snake_camel/blob/master/src/main/java/jp/toastkid/str20151223/Strings.java)

```java: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();
    }
```

```java: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 し、最後に新しい文字列オブジェクトを生成して返す、という単純な処理をするメソッドです。

----

# 比較・検証
せっかくなので実行速度を計測してみることにしました。検証用のコードは[こちら](https://github.com/toastkidjp/snake_camel/blob/master/src/main/java/jp/toastkid/str20151223/PerformanceChecker.java)です。なお、Collection フレームワークには GS Collections を使いました。


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

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

----

# 【追記】 JMH でのパフォーマンスチェック
その後、Java のコードのパフォーマンスチェックは [JMH](http://openjdk.java.net/projects/code-tools/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](https://github.com/google/guava/wiki/StringsExplained#caseformat) クラスを使いましょう。

## GitHub repository
- [snake_camel](https://github.com/toastkidjp/snake_camel)
- [snake_camel(JMH 導入版)](https://github.com/toastkidjp/snake_camel/tree/jmh)