web-dev-qa-db-ja.com

なぜこのJava 8プログラムがコンパイルされないのですか?

このプログラムは、Java 7(またはJava 8 with -source 7)、ただしJava 8:

interface Iface<T> {}
class Impl implements Iface<Impl> {}

class Acceptor<T extends Iface<T>> {
    public Acceptor(T obj) {}
}

public class Main {
    public static void main(String[] args) {
        Acceptor<?> acceptor = new Acceptor<>(new Impl());
    }
}

結果:

Main.Java:10: error: incompatible types: cannot infer type arguments for Acceptor<>
        Acceptor<?> acceptor = new Acceptor<>(new Impl());
                                           ^
    reason: inference variable T has incompatible bounds
      equality constraints: Impl
      upper bounds: Iface<CAP#1>,Iface<T>
  where T is a type-variable:
    T extends Iface<T> declared in class Acceptor
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Iface<CAP#1> from capture of ?
1 error

言い換えると、これはbackwardsソースの非互換性Java 7と8の間です。 Java SE 8とJava SE 7 間の非互換性ですが、私の問題に合うものは見つかりませんでした。

それで、これはバグですか?

環境:

$ /usr/lib/jvm/Java-8-Oracle/bin/Java -version
Java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
77
ghik

報告いただきありがとうございます。これはバグのように見えます。私はそれを世話し、おそらくこれがなぜ起こっているかについての情報が得られたら、おそらくより良い答えを追加します。このバグエントリ JDK-8043926 を提出して追跡しました。

20
Vicente Romero

Java言語仕様は型推論に関して大幅に変更されました。JLS7では、型推論が記述されました。 §15.12.2.7 および §15.12.2.8 では、JLS8では 第18章型推論 に特化した章全体があります。

JLS7とJLS8の両方で、ルールは非常に複雑です。違いを伝えることは困難ですが、セクション §18.5.2 から明らかなように、明らかに違いがあります。

この推論戦略は、Java SE 7 Edition of The Java Language Specification [..]]とは異なります。

ただし、変更の目的は、下位互換性を維持することでした。セクションの最後の段落を参照してください §18.5.2

[..]この戦略は、一般的なユースケースで合理的な結果を可能にし、Java SE 7 Edition of The Java Language仕様。

それが本当かどうかはわかりません。ただし、問題を示さないコードの興味深いバリエーションがいくつかあります。たとえば、次のステートメントはエラーなしでコンパイルされます。

new Acceptor<>(new Impl());

この場合、ターゲットタイプはありません。つまり、クラスインスタンス作成式ポリ式ではなく、型推論のルールよりシンプルです。 §18.5.2 を参照してください:

呼び出しがポリ式ではない場合、バインドされたセットB3 Bと同じである2

それが理由であり、次のステートメントが機能する理由です。

Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());

式のコンテキストには型がありますが、ターゲット型としてカウントされませんクラスインスタンス作成式割り当て式または呼び出し式、それはpoly式にはできません。 §15.9 を参照してください:

クラスインスタンス作成式は、クラスへの型引数にひし形を使用し、割り当てコンテキストまたは呼び出しコンテキスト(§5.2、§5.3)に表示される場合、ポリ式(§15.2)です。それ以外の場合は、スタンドアロン式です。

あなたの声明に戻って。 JLS8の関連部分は、再び §18.5.2 です。ただし、JLS8に従って次のステートメントが正しいかどうか、コンパイラーがエラーメッセージに問題がないかどうかはわかりません。しかし、少なくとも、さらなる情報を得るためのいくつかの選択肢と指針がありました。

Acceptor<?> acceptor = new Acceptor<>(new Impl());
40
nosid

型推論はJava 8.で変更されました。型推論は、コンストラクターとメソッドの両方について、ターゲット型とパラメーター型の両方を調べます。以下を考慮してください。

interface Iface {}
class Impl implements Iface {}
class Impl2 extends Impl {}

class Acceptor<T> {
    public Acceptor(T obj) {}
}

<T> T foo(T a) { return a; }

次は、Java 8では問題ありません(ただし、Java 7では問題ありません):

Acceptor<Impl> a = new Acceptor<>(new Impl2());

// Java 8 cleverly infers Acceptor<Impl>
// While Java 7 infers Acceptor<Impl2> (causing an error)

もちろん、これは両方でエラーを与えます:

Acceptor<Impl> a = new Acceptor<Impl2>(new Impl2());

これはJava 8:

Acceptor<Impl> a = foo (new Acceptor<>(new Impl2())); 

// Java 8 infers Acceptor<Impl> even in this case
// While Java 7, again, infers Acceptor<Impl2>
//   and gives: incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>

次の例では両方でエラーが発生しますが、エラーは異なります。

Acceptor<Impl> a = foo (new Acceptor<Impl2>(new Impl2()));

// Java 7:
// incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>

// Java 8:
// incompatible types: inferred type does not conform to upper bound(s)
//     inferred: Acceptor<Impl2>
//     upper bound(s): Acceptor<Impl>,Java.lang.Object

明らかに、Java 8は型推論システムをよりスマートにしました。これは非互換性の原因になりますか?一般に、いいえ。型消去のため、プログラムが推論される型コンパイルします。Java 8すべてのコンパイルJava 7プログラムですか?それはすべきですが、そうでない場合を提起しました。

起こっているように思われるのは、Java 8はワイルドカードをうまく処理していません。それらを制約の欠如と見なす代わりに、それを制限できない制約として扱っているようです。 JLSの手紙に従っているかどうかはわかりませんが、少なくとも精神的にはこれをバグと呼びます。

参考までに、これは機能します(私のAcceptorにはあなたのタイプの制約がありません):

Acceptor<?> a = new Acceptor<>(new Impl2());

あなたの例はメソッドパラメータの外側でワイルドカードタイプを使用していることに注意してください(これはお勧めできません)、メソッド呼び出しでダイヤモンド演算子を使用するより一般的なコードでも同じ問題が発生するのでしょうか? (多分。)

7