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.javaimport 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.javapublic 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 には多数のバグレポートが挙がっています。
- JDK-4488646 Java executable and System properties need to support Unicode on Windows
- JDK-6727466 java.exe/JRE1.6.0_10-b25/b27 doesn’t seem to handle international characters
- JDK-6584897 Cannot invoke class from command line with args containing non-ASCII characters
なんと 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]