この記事は Java Puzzlers Advent Calendar の8日目です。

・問題
最近は問題なくマルチバイト文字を含んだファイルを読み込めるようになった java.util.Properties ですが、昔はnative2ascii使ったりで面倒でした。
以下は、仕事で実際に遭遇したコードを元にしたものです。仕様的にはダメなわけですが、さて、実際に実行したら結果はどうなるでしょうか。

●プロパティファイル(Shift_JIS)

programmer=十把一絡げ

●プログラム

package com.bugworm.advent2016.puzzlers;

import java.io.InputStream;
import java.util.Properties;

public class MultiByteCharacterProperties{

    public static void main(String... args)throws Exception{
        Properties properties = new Properties();
        try(InputStream in = 
                ClassLoader.getSystemResourceAsStream("test.properties")){
            properties.load(in);
        }

        String value = properties.getProperty("programmer");
        String shift_jis = new String(value.getBytes("ISO8859-1"), "Shift_JIS");
        System.out.println("programmer は " + shift_jis);
    }
}

(1) 「programmer は 十把一絡げ」と出力される
(2) 実行時例外
(3) 「十把一絡げ」の部分が文字化けする






解答

(3) 「十把一絡げ」の部分が文字化けする

解説

この場合は文字化けするのですが、実は、たいていのケースではちゃんとマルチバイト文字を読み込めてしまいます。
しかし、上記の「十把一絡げ」はダメなのです。

「十把一絡げ」は、Shift_JISだと 「8F 5C 94 63 88 EA 97 8D 82 B0」となります。 
2バイト目の「5C」がくせものです。
単独で見るとこれは「\」になります。最初にPropertiesクラスが読み込む際にはShift_JISとして意識していませんので、これは実際に「\」として読みこまれます。
プロパティファイルでは「\」はエスケープ文字なので、「5C 94」部分がエスケープとみなされます。ただし「94」はエスケープとして特に意味がないのでそのまま「94」として解釈されます。
したがって読み込んだ結果を getBytes("ISO8859-1") すると「8F 94 63 88 EA 97 8D 82 B0」です。(5Cがなくなる)
なので、これをShift_JISとして文字列にすると、「8F 5C」が「諸」、3バイト目(元は4バイト目)の「63」は半角「c」なので、実行結果は「programmer は 諸c一絡げ」と表示されます。

ちなみに、 「十\把一絡げ」として、5Cを重ねてやれば、「8F 5C 5C 94 63 88 EA 97 8D 82 B0」となり、読み込み時に5Cが残る(5C 5Cをエスケープとみなして、5Cとして読み込まれる)ので、 結果的に「十把一絡げ」になります。