web-dev-qa-db-ja.com

推論変数には互換性のない境界があります。 Java 8コンパイラー回帰?

以下のプログラムは、Java 7およびEclipse Mars RC2でJava 8:

import Java.util.List;

public class Test {

    static final void a(Class<? extends List<?>> type) {
        b(newList(type));
    }

    static final <T> List<T> b(List<T> list) {
        return list;
    }

    static final <L extends List<?>> L newList(Class<L> type) {
        try {
            return type.newInstance();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Javac 1.8.0_45コンパイラを使用すると、次のコンパイルエラーが報告されます。

Test.Java:6: error: method b in class Test cannot be applied to given types;
        b(newList(type));
        ^
  required: List<T>
  found: CAP#1
  reason: inference variable L has incompatible bounds
    equality constraints: CAP#2
    upper bounds: List<CAP#3>,List<?>
  where T,L are type-variables:
    T extends Object declared in method <T>b(List<T>)
    L extends List<?> declared in method <L>newList(Class<L>)
  where CAP#1,CAP#2,CAP#3 are fresh type-variables:
    CAP#1 extends List<?> from capture of ? extends List<?>
    CAP#2 extends List<?> from capture of ? extends List<?>
    CAP#3 extends Object from capture of ?

回避策は、変数をローカルに割り当てることです。

import Java.util.List;

public class Test {

    static final void a(Class<? extends List<?>> type) {

        // Workaround here
        List<?> variable = newList(type);
        b(variable);
    }

    static final <T> List<T> b(List<T> list) {
        return list;
    }

    static final <L extends List<?>> L newList(Class<L> type) {
        try {
            return type.newInstance();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

型の推論は、Java 8( 例:JEP 101 "generalized target-type inference" )で大きく変化したことを知っています。新しい言語の「機能」?

[〜#〜] edit [〜#〜]:これもJI-9021550としてOracleに報告しましたが、念のため、 Java 8、Eclipseにも問題を報告しました:

21
Lukas Eder

バグレポート に感謝します。回答の例については、Holgerに感謝します。これらと他のいくつかはついに、11年前にEclipseコンパイラで行われた1つの小さな変更を疑問視させました。ポイントは、Eclipseがキャプチャアルゴリズムを違法に拡張して、ワイルドカードの境界に再帰的に適用することでした。

この違法な変更が、Eclipseの動作をjavacと完全に整合させた1つの例がありました。 Eclipseの世代の世代は、JLSで明確に見ることができるものよりも、この古い決定を信頼しています。今日、以前の逸脱には別の​​理由があったに違いないと考えています。

今日、この点でecjをJLSに合わせるために勇気を出しました。非常にクラックしにくいと思われる5つのバグは、基本的にそのように解決されました(さらに、補償としてあちこちで微調整します)。

Ergo:はい、Eclipseにはバグがありましたが、そのバグは4.7マイルストーン2で修正されました:)

以下は、ecjが今後報告する内容です。

The method b(List<T>) in the type Test is not applicable for the arguments (capture#1-of ? extends List<?>)

互換性を検出するルールを見つけられないのは、キャプチャバウンド内のワイルドカードです。より正確には、推論中(正確には組み込み)に、次の制約(T#0は推論変数を表します)に遭遇します。

⟨T#0 = ?⟩

単純に、型変数をワイルドカードに解決することはできますが、おそらくワイルドカードは型と見なされないため、リダクションルールは上記をFALSEに還元するものとして定義し、推論が失敗するようにします。

6

免責事項-私は主題について十分に知りません、そして、以下はjavacの振る舞いを正当化しようとする私の非公式の推論です。


問題を減らすことができます

<X extends List<?>> void a(Class<X> type) throws Exception
{
    X instance = type.newInstance();
    b(instance);  // error
}

<T> List<T> b(List<T> list) { ... }

Tを推測するには、制約があります

      X <: List<?>
      X <: List<T>

基本的に、これは解決できません。たとえば、X=List<?>の場合、Tは存在しません。

Java7がこのケースをどのように推測するかわかりません。しかし、javac8(およびIntelliJ)は「合理的に」振る舞うと思います。


さて、どうしてこの回避策が機能するのでしょうか?

    List<?> instance = type.newInstance();
    b(instance);  // ok!

ワイルドカードキャプチャにより機能します。これにより、instanceのタイプを「絞り込み」、より多くのタイプ情報が導入されます。

    instance is List<?>  =>  exist W, where instance is List<W>  =>  T=W

残念ながら、これはinstanceXの場合は行われないため、処理する型情報が少なくなります。

おそらく、言語も「改善」されて、Xのワイルドカードキャプチャも可能になります。

    instance is X, X is List<?>  =>  exist W, where instance is List<W>
7
ZhongYu

bayou.ioの回答 のおかげで、次の事実に問題を絞り込むことができます。

<X extends List<?>> void a(X instance) {
    b(instance);  // error
}
static final <T> List<T> b(List<T> list) {
    return list;
}

エラーを生成します

<X extends List<?>> void a(X instance) {
    List<?> instance2=instance;
    b(instance2);
}
static final <T> List<T> b(List<T> list) {
    return list;
}

問題なくコンパイルできます。 instance2=instanceの割り当ては、メソッド呼び出し引数に対しても発生するはずの拡大変換です。 this answer のパターンとの違いは、追加のサブタイプ関係です。


この特定のケースがJava Language Specificationに沿っているかどうかはわかりませんが、一部のテストでは、Eclipseがコードを受け入れているのは、一般的に、一般的な型は、次のように間違いなく正しくないため、エラーや警告なしにコードをコンパイルできます。

public static void main(String... arg) {
    List<Integer> l1=Arrays.asList(0, 1, 2);
    List<String>  l2=Arrays.asList("0", "1", "2");
    a(Arrays.asList(l1, l2));
}
static final void a(List<? extends List<?>> type) {
    test(type);
}
static final <Y,L extends List<Y>> void test(List<L> type) {
    L l1=type.get(0), l2=type.get(1);
    l2.set(0, l1.get(0));
}
5
Holger