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の進化の過程で、過去の遺物が足かせになってきた。そんな感じです。