ECJ (Eclipse Compiler for Java) は面倒見が良すぎ…

Eclipse (の JDT(Java Development Tools)) の独自コンパイラ ECJ (Eclipse Compiler for Java) の面倒見がよすぎて、JDK付属のコンパイラ javac にお願いしたら怒られる…

確認したのは、JDK 1.6.0_20、Eclipse SDK 3.5.0 *1

BOM付きUTF-8のjavaソースファイル

UTF-8の規格上は、BOMが付いていてもOK。

ただ、javac は「Hoge.java:1: \65279 は不正な文字です。」とエラーになる。

Eclipse では、問題なくコンパイルできる。さらに困ったことに、エンコードは判断できるがBOM付きかの判断が出来ない(どちらでも同じようにエディタ部に表示される)。

アノテーションでの各要素の値で配列の最後にカンマ

通常の配列の場合は、最後にカンマがあっても無視される。

String str[] = { "aa", "bb", "cc", };

言語仕様でもそう定義されている。

A trailing comma may appear after the last expression in an array initializer and is ignored.

The Java Language Specification, Third Edition - 10.6 Array Initializers


しかし、アノテーション(の要素の値)の場合、javac では「Hoge.java:1: 式の開始が不正です。」とエラーになる。

@XmlType(propOrder = { "aa", "bb", "cc", })
public class Hoge {


配列の初期化なんだから、通常の場合と同様に最後のカンマは無視されてよさそうなんだけど…。

代入演算子での Integer 変数への Double 変数の加算

次のような代入演算子の計算が、ECJ ではエラーとならない。

Integer i = 1;
Double d = 2.0D;

i += d;

javac の場合は次のようなエラーになる。

Hoge.java:12: 変換できない型
検出値  : double
期待値  : java.lang.Integer
	i += d;
	     ^

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)( (E1) op (E2) ), where T is the type of E1, except that E1 is evaluated only once.

The Java Language Specification, Third Edition - 15.26.2 Compound Assignment Operators

とあるので、「i += d;」は「i = (Integer) ( (i) + (d) );」の様に展開されることになる。この場合、ECJ でも javac でもエラーとなる。


ECJ でビルドしたクラスをデコンパイルすると、「i = Integer.valueOf( (int)( (double)i.intValue() + d.doubleValue() ) );」となっている。
でも、そもそも double の値を Integer (int) 変数にセットするのって危険な気が…。

ジェネリックパラメータ その1

次のような generic を使ったソースの場合、

	public <T> T getValue(String key) {
		return getValueSub(key);
	}
	public <T> T getValueSub(String key) {
		return null;
	}

javac の場合は次のようなエラーになる。

Hoge.java:13: 型パラメータ <T>T を判別できません; 型変数 T (上限 T,java.lang.Object) の固有の最大インスタンスが存在しません。
		return getValueSub(key);
		                  ^

「return this.getValueSub(key);」の様に型を指定すればエラーなく通る。

ジェネリックパラメータ その2

その1と似てるけど、プリミティブ型で受け取る場合。

	private Object value;

	private <T> void setValue(T value) {
		this.value = value;
	}

	private <T> T getValue() {
		return (T) value;
	}

	private void exec() {
		setValue(123);
		int result = this.getValue();
		System.out.println(result);
	}

その1と同様に「this.getValue();」と型を指定するか、「Integer result」と戻り値をプリミティブでなくオブジェクトにする。

拡張for文 (foreach文) のキャスト

コンパイル時と言うより、生成されるクラスファイルの違いだが。


次のようなソースをそれぞれでコンパイルし実行した場合(generic の警告は無視)。

	public static void main(String[] args) {
		ArrayList list = new ArrayList<Long>();
		list.add(1L);
		list.add(2L);

		ArrayList<Double> dlst = list;
		for(Number num : dlst) {
			System.out.print(num);
			System.out.println();
		}

	}

Eclipse でビルドした場合は正常に実行できるけど、javac でビルドした場合は「Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Double」と例外が発生して実行できない。


ビルドされたクラスをそれぞれデコンパイルすると以下のようになる。

// from ECJ

        for(Iterator iterator = dlst.iterator(); iterator.hasNext(); System.out.println())
        {
            Number num = (Number)iterator.next();
            System.out.print(num);
        }
// from javac

        for(Iterator iterator = arraylist1.iterator(); iterator.hasNext(); System.out.println())
        {
            Double double1 = (Double)iterator.next();
            System.out.print(double1);
        }

それぞれ iterator.next() から得た値のキャストで、ECJは「foreach文で指定した型」に、javacは「変数(の型総称)の型」に、となる。

Enum<?> へのキャスト

※TBをもらって知ったので追記。もっとも 6u18 で修正されてるので、上記の確認した環境では再現できない(6u01 では確認)。

public class EnumCast {

	private enum Planet { EARTH, MARS }

	public static void main(String[] args) {
		Enum<?> e1 = Planet.EARTH;
		// Hmm, doesn't compile
		Planet e2 = (Planet) e1;
		// Well this compiles, but it's not pretty!
		Planet e3 = (Planet) (Object) e1;
		// So does this ....
		Planet e4 = (Planet) (Enum<?>) e1;

		System.out.println("e=" + e1 + e2 + e3 + e4);
	}

}

「Enum<?> e1 = Planet.EARTH;」や「(Planet) (Enum<?>) e1」と言ったところで「変換できない型」となる。
もっとも「Enum<?>」とせずに「Enum」とするとエラーにならないので、ものすごく javac のバグくさい気が…。

参考

*1:個別には、Eclipse Project SDK Version: 3.5.0.v20090423、Eclipse Java Development Tools Version: 3.5.0.v20090527