<T> T hoge(String a, T... b) なメソッドに引数を1つしか渡さなかった場合の挙動が ECJ, JDK で異なる
Class object を渡さずに戻り値の型を指定したい - f99aq8oveのブログ というエントリーを先日書いたわけですが、問題があることがわかりました。
まずはこれを見てください。
__GooglePrettify__
package net.f99aq8ove.junk;
import java.util.HashMap;
import java.util.Map;
public class HogeContainer {
private final Map<String, Object> container = new HashMap<String, Object>();
public void put(String key, Object obj) {
container.put(key, obj);
}
public <E> E get1(String key, Class<E> klass) {
Object obj = container.get(key);
if (klass.isInstance(obj)) {
return klass.cast(obj);
} else {
return null;
}
}
public <E> E get2(String key, @SuppressWarnings("unchecked") E... dummy) {
@SuppressWarnings("unchecked")
Class<E> klass = (Class<E>) dummy.getClass().getComponentType();
Object obj = container.get(key);
if (klass.isInstance(obj)) {
return klass.cast(obj);
} else {
return null;
}
}
public static void main(String[] args) {
HogeContainer hoge = new HogeContainer();
hoge.put("string", "hoge");
hoge.put("integer", 1);
String str1 = hoge.get1("string", String.class);
String str2 = hoge.get2("string");
System.out.printf("%s, %s\n", str1, str2);
// hoge, hoge
Integer int1 = hoge.get1("integer", Integer.class);
Integer int2 = hoge.get2("integer");
System.out.printf("%d, %d\n", int1, int2);
// 1, 1
Long long1 = hoge.get1("string", Long.class);
Long long2 = hoge.get2("string");
System.out.printf("%d, %d\n", long1, long2);
// null, null
int1 = hoge.get1("string", Integer.class);
int2 = hoge.get2("string");
System.out.printf("%d, %d\n", int1, int2);
// null, null
}
}↑なのですが、Oracle の JDK でコンパイルすると、以下のように ClassCastException でこけます。
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
at net.f99aq8ove.junk.HogeContainer.main(HogeContainer.java:49)Eclipse で書いて実行した時には問題なく動作したのに、JDK でコンパイルすると動作しないのはナゼか…。
ところで、Eclipse でコンパイルした場合、JDK ではなく、ECJ (Eclipse Compiller for Java) が使用されます。
ということは、コンパイラの仕様差かなということで、ECJ, JDK でコンパイルした class ファイルを JD で逆コンパイルして眺めてみます。
ECJ
__GooglePrettify__
package net.f99aq8ove.junk;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
public class HogeContainer
{
private final Map<String, Object> container = new HashMap();
public void put(String key, Object obj) {
this.container.put(key, obj);
}
public <E> E get1(String key, Class<E> klass) {
Object obj = this.container.get(key);
if (klass.isInstance(obj)) {
return klass.cast(obj);
}
return null;
}
public <E> E get2(String key, E[] dummy)
{
Class klass = dummy.getClass().getComponentType();
Object obj = this.container.get(key);
if (klass.isInstance(obj)) {
return klass.cast(obj);
}
return null;
}
public static void main(String[] args)
{
HogeContainer hoge = new HogeContainer();
hoge.put("string", "hoge");
hoge.put("integer", Integer.valueOf(1));
String str1 = (String)hoge.get1("string", String.class);
String str2 = (String)hoge.get2("string", new String[0]);
System.out.printf("%s, %s\n", new Object[] { str1, str2 });
Integer int1 = (Integer)hoge.get1("integer", Integer.class);
Integer int2 = (Integer)hoge.get2("integer", new Integer[0]);
System.out.printf("%d, %d\n", new Object[] { int1, int2 });
Long long1 = (Long)hoge.get1("string", Long.class);
Long long2 = (Long)hoge.get2("string", new Long[0]);
System.out.printf("%d, %d\n", new Object[] { long1, long2 });
int1 = (Integer)hoge.get1("string", Integer.class);
int2 = (Integer)hoge.get2("string", new Integer[0]);
System.out.printf("%d, %d\n", new Object[] { int1, int2 });
}
}OpenJDK 7
__GooglePrettify__
package net.f99aq8ove.junk;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
public class HogeContainer
{
private final Map<String, Object> container = new HashMap();
public void put(String paramString, Object paramObject) {
this.container.put(paramString, paramObject);
}
public <E> E get1(String paramString, Class<E> paramClass) {
Object localObject = this.container.get(paramString);
if (paramClass.isInstance(localObject)) {
return paramClass.cast(localObject);
}
return null;
}
public <E> E get2(String paramString, E[] paramArrayOfE)
{
Class localClass = paramArrayOfE.getClass().getComponentType();
Object localObject = this.container.get(paramString);
if (localClass.isInstance(localObject)) {
return localClass.cast(localObject);
}
return null;
}
public static void main(String[] paramArrayOfString)
{
HogeContainer localHogeContainer = new HogeContainer();
localHogeContainer.put("string", "hoge");
localHogeContainer.put("integer", Integer.valueOf(1));
String str1 = (String)localHogeContainer.get1("string", String.class);
String str2 = (String)localHogeContainer.get2("string", new Object[0]);
System.out.printf("%s, %s\n", new Object[] { str1, str2 });
Integer localInteger1 = (Integer)localHogeContainer.get1("integer", Integer.class);
Integer localInteger2 = (Integer)localHogeContainer.get2("integer", new Object[0]);
System.out.printf("%d, %d\n", new Object[] { localInteger1, localInteger2 });
Long localLong1 = (Long)localHogeContainer.get1("string", Long.class);
Long localLong2 = (Long)localHogeContainer.get2("string", new Object[0]);
System.out.printf("%d, %d\n", new Object[] { localLong1, localLong2 });
localInteger1 = (Integer)localHogeContainer.get1("string", Integer.class);
localInteger2 = (Integer)localHogeContainer.get2("string", new Object[0]);
System.out.printf("%d, %d\n", new Object[] { localInteger1, localInteger2 });
}
}ダラっと書き出してしまいましたが、問題の箇所だけ抜き出すと…
__GooglePrettify__
// Original
Long long2 = hoge.get2("string");
// ECJ
Long long2 = (Long)hoge.get2("string", new Long[0]);
// JDK
Long localLong2 = (Long)localHogeContainer.get2("string", new Object[0]);はい。可変引数に渡されている配列が、Object 型になってしまっていますね。
これでは get2 内の判定処理が意図通りに動きません。
ちなみに、1つ以上の引数を渡してあげると new Long が渡されます。
__GooglePrettify__
// Original
Long long2 = hoge.get2("string", 1L);
// ECJ
Long long2 = (Long)hoge.get2("string", new Long[] { Long.valueOf(1L) });
// JDK
Long localLong2 = (Long)localHogeContainer.get2("string", new Long[] { Long.valueOf(1L) });仕様をあたってみても k = n の時の定義が曖昧な感じがするので、実装依存という事だと思われます。
Chapter 15. Expressions
結論
get2 は諦めましょう ^^;