JavaのUnicode入出力問題を回避する

Java の文字列内部表現は UTF-16 となっており Unicode を扱うことができます。ですが プラットフォーム OS との境界部分となるコマンドライン引数とコンソール出力についてはプラットフォーム OS の文字コードとの変換がおこなわれるため Unicode を扱えない部分があります。

Windows Java における Unicode 入出力問題と回避方法について紹介します。

下記の動作は Windows 版の Java 11.0.5 で確認しました。
他のバージョンでは動作が異なることがあるかもしれません。

Windows 版の Java はコマンドライン引数で Unicode を扱えない

Java プログラムのエントリーポイントである main メソッドのシグネチャは以下のようになっており プログラム起動時のコマンドライン引数を String 配列として受け取ります。

public static void main(String[] args)

String の内部形式は UTF-16 ですから Java プログラムのエントリーポイントは Unicode に対応しているように思えます。しかし 実際は main メソッドが呼ばれる手前の java.exe ではコマンドライン引数をマルチバイトキャラクタ―セット MBCS で扱っているため Unicode 固有の文字が欠落するという問題を抱えています。

日本語 Windows の場合 マルチバイトキャラクタ―セット MBCS の文字コードは MS932 ≒Shift_JIS です。つまり Windows 版の java.exe がコマンドライン引数として扱える文字はシフト JIS の範囲に制限されていることになります。

簡単なプログラムを作って確認してみましょう。コマンドライン引数を画面に表示するだけのプログラムです。

Sample1.java
import java.awt.Font; import javax.swing.JFrame; import javax.swing.JLabel; import static javax.swing.SwingConstants.*; public class Sample1 { public static void main(String[] args) { StringBuilder text = new StringBuilder(); for(String arg : args) { text.append(arg + " "); } JFrame frame = new JFrame("Sample1"); frame.setSize(320, 240); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JLabel label = new JLabel(text.toString()); label.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16)); label.setHorizontalAlignment(CENTER); frame.getContentPane().add(label); frame.setLocationRelativeTo(null); frame.setVisible(true); } }

このプログラムをコンパイルして実行可能 JAR ファイルを作成します。

コマンドプロンプト
C:¥>javac Sample1.java C:¥>jar cfe Sample1.jar Sample1 Sample1.class

コマンドライン引数に Unicode 固有の文字を指定してプログラムを起動します。今回は シールを剝がす という文字列をコマンドライン引数に指定しました。 という文字は の旧字でシフト JIS には含まれていない Unicode 固有の文字です。

コマンドプロンプト
C:¥>javaw -jar Sample1.jar シールを剝がす

の文字だけ化けてしまっていますね。

他にも中国語などが文字化けしてしまいます。ニーハオを変換してコマンドライン引数に 你好 を指定してみましょう。

コマンドプロンプト
C:¥>javaw -jar Sample1.jar 你好

中国語もダメでした。

Windows 版の Java がコマンドライン引数で Unicode を扱えていないことが分かりました。

Windows 版の Java はコンソールに Unicode を出力できない

次は出力のほうを確認してみましょう。Java の標準出力 標準エラー出力は既定でプラットフォーム OS の文字コードに変換されるため Unicode 出力で問題が出ます。日本語 Windows の場合は MS932 ≒Shift_JIS に変換されて出力されるので このとき一部の文字が文字化けしてしまいます。

また簡単なプログラムを作って確認してみましょう。

Sample2.java
public class Sample2 { public static void main(String[] args) { System.out.println("シールを剝がす"); System.out.println("你好"); } }

今回はソースコードに Unicode を含んでいるのでファイルを UTF-8 形式で保存してコンパイル時に文字コードを指定します。

コマンドプロンプト
C:¥>javac -encoding UTF-8 Sample2.java C:¥>jar cfe Sample2.jar Sample2 Sample2.class

コマンドプロンプトで実行してみます。

コマンドプロンプト
C:¥>java -jar Sample2.jar シールを?がす ?好

やはり文字化けしてしまいますね。これはコマンドプロンプトのコードページが MS932 になっているのが原因かもしれません。

コマンドプロンプトのコードページを UTF-8 65001 に変更してみましょう。

コマンドプロンプト
C:¥>chcp 65001 Active code page: 65001 C:¥>java -jar Sample2.jar V[? ?D

コマンドプロンプトのコードページを UTF-8 65001 に変更したら文字化けがひどくなってしまいました😭

回避方法 1 file.encoding プロパティを指定する

Java には file.encoding というデフォルトのエンコーディングを指定するプロパティがあります。このプロパティはファイルだけでなくコンソール 標準出力 標準エラー出力 に対しても作用します。

デフォルトエンコーディングに UTF-8 を指定して実行してみましょう。UTF-8 を表示できるようにコマンドプロンプトのコードページも UTF-8 65001 に変更しておきます。

コマンドプロンプト
C:¥>chcp 65001 Active code page: 65001 C:¥>java -Dfile.encoding=UTF-8 -jar Sample2.jar シールを剝がす 你好

おおっ!上手くいきました!

コンソールのコードページを UTF-8 に変更して さらに file.encoding プロパティで UTF-8 を指定すれば Windows でもコンソールに Unicode を出力することができました。これは Unicode 出力問題の回避策の 1 つになりますね。

ただし file.encoding プロパティはコンソール出力だけでなく 他ファイルのデフォルトエンコーディングとしても作用することに注意してください。

なお file.encoding プロパティはコマンドライン引数の扱いには影響しないようでコマンドライン引数の文字化けは改善しませんでした。

回避方法 2 exewrap を使う

コマンドライン引数の文字が化けてしまうのは JavaVM の問題ではなく起動を担当している java.exe javaw.exe の問題です。java.exe javaw.exe を使わずに Unicode に対応した Java プログラム起動方法を使えば文字化けは発生しないはずです。

exewrap を使ってみましょう。

まずは 最初に試した Sample1.jar exewrap で実行ファイルにします。

コマンドプロンプト
C:¥>exewrap -g Sample1.jar exewrap 1.6.0 for x64 (64-bit) Architecture: x64 (64-bit) Target: Java 5.0 (1.5.0.0) Sample1.exe (64-bit) version 0.0.0.0

出来上がった Sample1.exe にコマンドライン引数 シールを剝がす 你好 それぞれを指定して実行してみます。

コマンドプロンプト
C:¥>Sample1.exe シールを剝がす

コマンドプロンプト
C:¥>Sample1.exe 你好

どちらも上手くいきましたね。exewrap はコマンドライン引数をマルチバイト文字セット MBCS ではなくワイドキャラクターで扱っているため main メソッドの引数に引き渡すまでの過程で Unicode 固有の文字が欠落してしまうことがありません。

また exewrap ではコマンドライン引数だけでなくコンソールへの Unicode 出力も改善します。

コンソール出力を試した Sample2.jar exewrap で実行ファイルにします。

コマンドプロンプト
C:¥>exewrap Sample2.jar exewrap 1.6.0 for x64 (64-bit) Architecture: x64 (64-bit) Target: Java 5.0 (1.5.0.0) Sample2.exe (64-bit) version 0.0.0.0

出来上がった Sample2.exe をコードページ 65001 UTF-8 に設定したコマンドプロンプトで実行してみましょう。

コマンドプロンプト
C:¥>chcp 65001 Active code page: 65001 C:¥>Sample2.exe シールを剝がす 你好

文字化けせずに Unicode 固有の文字がコンソールに出力されました。file.encoding プロパティも必要としていません。exewrap Java の文字列 Unicode をコンソールのコードページに合わせて変換して出力してくれるからです。コードページが 65001 UTF-8 になっていれば Unicode UTF-16 UTF-8 に変換して出力するので Unicode 固有の文字が欠落しません。

コマンドプロンプト
C:¥>chcp 932 現在のコード ページ: 932 C:¥>Sample2.exe シールを?がす ?好

exewrap Windows Java Unicode 入出力問題の良い解決策の 1 つです。

これって Windows Java のバグじゃないんですか?

Unicode 固有の文字をコマンドライン引数として渡すことができない これってバグのようにも思えますよね。実際 JDK Bug System には多数のバグレポートが挙がっています。

なんと 2001 年というかなり古いものまでありました。オープン状態のまま 20 年も放置されてたり Won’t Fix 修正しない で解決済としてクローズされていたり。ワイドキャラクタ―を渡すことができないなら せめて ¥uxxxx エスケープに対応してくれ! といった意見もありましたが対応される様子もなさそうです。

システムの文字コードが適切なら文字化けしないんじゃないの? というのが修正しない理由の 1 つになっているようです。

つまり 日本語 Windows の文字コードが MS932 ≒Shift_JIS になっているのが悪いと? システムのデフォルト文字コードが EUC-JP から UTF-8 に変わっていった Linux ディストリビューションではこの問題の影響を受けないようですね。システムの文字コードが UTF-8 なら Java 文字列 UTF-16 への変換で欠落はしないと。

なるほど。たしかに一理ありますね。いつまでもシフト JIS にしがみついてちゃいかんのか。

Windows システムの文字コードを UTF-8 にしてみる

Windows 10 バージョン 1803 からシステムロケールを UTF-8 に変更するベータ機能が追加されました。

これを有効にするとマルチバイト文字セット MBCS MS932 ≒Shift_JIS から UTF-8 に変更されるらしいです。変更後はシステムの再起動が必要です。

それでは システムロケールを UTF-8 に変更した Windows で動作を確認してみましょう。

まず コマンドプロンプトを起動した時点で既にコードページが 65001 UTF-8 になっています。これは期待できそうですねえ。

コマンドプロンプト
Microsoft Windows [Version 10.0.18363.778] (c) 2019 Microsoft Corporation. All rights reserved. C:¥>chcp Active code page: 65001

サンプルプログラムをコンパイルして実行してみます。

コマンドプロンプト
C:¥>javac Sample1.java C:¥>jar cfe Sample1.jar Sample1 Sample1.class C:¥>javaw -jar Sample1.jar シールを剝がす

え?

盛大に文字化けしてしまっているんですが?

Windows の文字コードを UTF-8 に変更するベータ機能 動作がおかしくなるアプリケーションがあるとは聞いていたのですが まさか Java もそのうちの 1 つだったとは…。

ベータが外れて正式な機能になれば Windows の文字コードを UTF-8 に切り替えていくユーザーも増えるでしょうから今後の Java の改善に期待ですね。


ちなみに exewrap EXE 化している場合は Windows の文字コードを UTF-8 に変更していても文字化けせずに正しく動きました。

コマンドプロンプト
C:¥>Sample1.exe シールを剝がす

結論 2020 5 月現在

Windows のシステムロケールを UTF-8 に変更しても Java のコマンドライン引数に Unicode を渡せるようにはならないので exewrap を使いましょう[PR]

この記事を共有しませんか?