ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

Javaで文字コード変換時のUnsupportedEncodingExceptionを回避する

Java文字コードを変換するコードを書く

Java文字コードを指定するケースって、あんまり遭遇しませんが、業務アプリを作ってると、たまーに文字コードを指定するケースに遭遇します。

例えば、文字列をUTF-8に変換するユーティリティ・メソッドを作成します。

private static final Logger LOGGER = LoggerFactory.getLogger(CharacterEncodingUtils.class);

public static byte[] toUTF8(String source) {
    try {
        return source.getBytes("utf-8");
   } catch (UnsupportedEncodingException e) {
        LOGGER.error("String to UTF-8 convert failed.", e);
        throw new RuntimeException(e);
    }
}

よくあるコードです。このコードを見るたびに、「絶対に発生しないUnsupportedEncodingExceptionの例外処理がダサい」と思うのは私だけでしょうか。

これをイケてるコードに修正します。

Javaエンコード指定方法

StringクラスのgetBytesメソッドや、ファイル入出力で使うInputStreamReaderやOutputStreamWriterのコンストラクタでは、引数に文字コードセット名を文字列で指定できますが、文字コードセット名ではなく、Charsetオブジェクトも指定可能です。

文字コードセット名を文字列で指定する場合、存在しない名前が指定されたときにUnsupportedEncodingExceptionが発生します。
しかし、Charsetオブジェクトで指定する場合、存在しない文字コードセットのCharsetオブジェクトなんて作れないので、例外が発生しないのです。

StandardCharsetsクラスを利用する

Java7で追加されたStandardCharsetsクラスには、一部の文字コードに対応するCharsetsオブジェクトが定数宣言されています

定数 文字コードセット
ISO_8859_1 ISOラテン・アルファベット、数字
US_ASCII 7ビットASCII
UTF_16 16ビットUnicode (UTF-16)
UTF_16BE ビッグエンディアン・バイト順の16ビットUnicode (UTF-16)
UTF_16LE トルエンディアン・バイト順の16ビットUnicode (UTF-16)
UTF_8 8ビットUnicode (UTF-8)

StandardCharsetsクラスに用意されている文字コードセットであれば、最もイケてるコードにすることができます。

public static byte[] toUTF8(String source) {
    return source.getBytes(StandardCharsets.UTF_8);
}
CharsetクラスのforNameメソッドを利用する

では、StandardCharsetsクラスに用意されていない文字コードセットを指定したい場合、どうすればよいでしょう。
CharsetクラスのforNameメソッドを使用します。このメソッドの引数に文字コードセット名を文字列で指定します。

public static byte[] toUTF83(String source) {
    return source.getBytes(Charset.forName("utf-8"));
}

文字コードセット名を文字列で指定しているのに、例外処理は実装していません。Charsetを使う場合、存在しない文字コードセット名も存在できるかというと、そんなわけもなく、forNameメソッドに存在しない文字コードセット名を指定すると、IllegalCharsetNameExceptionが発生します。

ポイントは、UnsupportedEncodingExceptionは検査例外だけど、IllegalCharsetNameExceptionは非検査例外であることです。

非検査例外は例外処理の実装は任意なので、今回のように文字コードセット名を定数で指定している場合は例外処理を実装せず、動的に文字コードセット名が変わる場合だけ例外処理を実装すればよいのです。

イケてるコードを書きましょう

同じ文字コードセット名を文字列で指定するメソッドなのに、発生する例外が違う。しかも片方は検査例外なのに、もう片方は非検査例外。たまにあるJavaの一貫性のなさです。
今回は、ちょっとした工夫で、ダサいコードをイケてるコードに変更しました。ちょっとしたことですが、知っているとコードレビューでドヤ顔できます。

検査例外オワコン説

最後に今回の内容とはずれますが、検査例外ってオワコンかな、って感じます。

例外処理って一か所に集約したほうが楽だし、何より、コードの見通しが良くなります。開発者は業務ロジックの実装に専念すべきです。テストも楽になりますしね。

だったら何も考えず、メソッドにthrows Exceptionをつけて上位にスローすればいいじゃん、っていう人もいますが、そんな単純な話ではないです。
例えば、メソッドでStreamで処理を実装した場合、Streamの中で検査例外が発生するコードを書くとコンパイルエラーになります。そんな時は結局、Streamの中で検査例外をキャッチして、非検査例外にラップしてリスローするコードを書くことになります。

検査例外のおかげで「エラー処理書き忘れるとコンパイルエラーになる。Java最高!」って思った時期もありましたが、Stream処理が実装されたあたりから、検査例外がうっとおしくて仕方ありません。
Javaの進化の過程で、過去の遺物が足かせになってきた。そんな感じです。