ひさびさにJavaの事を書きます。(前回いつだっけ?)
内容はMapクラスで想定外の動きが発生したのでまとめました。
ひとまず何が起きたかを言うと、Map<String,String>で宣言した変数からキーを指定して値を取得しようとするとClassCastExceptionが発生した。
ソースコードは以下の通りです。(実行環境はJava8です)
Map<String,String> mapValue=makeMap(); for(String key:mapValue.keySet()){ System.out.println(mapValue.get(key)); }
private static Map<String,String> makeMap(){ Map<String,String> mapValue=new HashMap<>(); String value="{111111111111111111111110:1234}"; mapValue=JSON.decode(value); return mapValue; }
このロジックの中の標準出力のときに何故かBigDecimalはStringにキャストできないとエラーが出る。
結論からいうと「mapValueの値はStringであることは保証されない」でした。
JSON.decode(value)でJSON形式の文字列を変換しているのですが、値が数値扱いの記述されているので変換後の変数はMap<String,String>ではなくMap<String,BigDecimal>になる。
では変数の型宣言や戻り値で形式が定義されているのにもかかわらず、継承関係に無い異なる型が入るのかについて調べて見ます。
まずはJavaファイルからClassファイルに変換されたときにどうなるかを見てみました。
private static Map makeMap()
{
Map mapValue = new HashMap();
String value = “{111111111111111111111110:1234}”;
mapValue = (Map)JSON.decode(value);
return mapValue;
}
・・・まさかの型の宣言がなくなっている!!
そういえばMapって型宣言なくてもエラーは出ないんだった。
そしてmainのロジックはこうなる。
public static void main(String args[])
{
Map mapValue = makeMap();
String key;
for(Iterator iterator = mapValue.keySet().iterator(); iterator.hasNext(); System.out.println((String)mapValue.get(key)))
key = (String)iterator.next();
}
当然Mapのkeyとvalueの型はないので、エラーは出ずにmakeMapから変数を受け取ることができる。
ただし、ソースコード上Stringで宣言されているのでStringのキャストが行われている。(ここで型が適用されるとは・・・)
ここでエラーが発生する。(確かにそうなるよね)
ちなみにMap<String,Integer>の場合はこうなりました。
public static void main(String args[])
{
Map mapValue = makeMap();
String key;
for(Iterator iterator = mapValue.keySet().iterator(); iterator.hasNext(); System.out.println(mapValue.get(key)))
key = (String)iterator.next();
}
自分で実装する場合は変数に使用される型はまだ意識しますが、JSONの変換など意外に気が付かないところで発生しそうなことなので意外に特定に時間がかかりました。