7

私が次のものを持っているとしましょう:

public interface Filter<E> {
     public boolean accept(E obj);
}

import java.io.File;
import java.io.FilenameFilter;

public abstract class CombiningFileFilter extends javax.swing.filechooser.FileFilter
        implements java.io.FileFilter, FilenameFilter {

    @Override
    public boolean accept(File dir, String name) {
        return accept(new File(dir, name));
    }
}

現状では、javac を使用してコンパイルできますCombiningFileFilter。ただし、 にも実装することにしFilter<File>CombiningFileFilter場合は、次のエラーが発生します。

CombiningFileFilter.java:9: error: reference to accept is ambiguous, 
both method accept(File) in FileFilter and method accept(E) in Filter match
                return accept(new File(dir, name));
                       ^
  where E is a type-variable:
    E extends Object declared in interface Filter
1 error

ただし、3番目のクラスを作成すると:

import java.io.File;

public abstract class AnotherFileFilter extends CombiningFileFilter implements
        Filter<File> {
}

コンパイルエラーはなくなりました。Filter一般的でない場合、コンパイルエラーもなくなります。

public interface Filter {
    public boolean accept(File obj);
}

Filter<File>クラスが を実装しているので、acceptメソッドは実際にはそうあるべきaccept(File)であり、あいまいさがないことをコンパイラが理解できないのはなぜですか? また、このエラーが javac でのみ発生するのはなぜですか? (Eclipse のコンパイラでは問題なく動作します。)

/edit
3 番目のクラスを作成するよりも、このコンパイラの問題に対するより明確な回避策は、public abstract boolean accept(File)メソッドを に追加することCombiningFileFilterです。それはあいまいさを消去します。

/e2
JDK 1.7.0_02 を使用しています。

4

2 に答える 2

9

私が知る限り、コンパイルエラーは Java 言語仕様によって義務付けられており、次のように書かれています。

Cは正式な型パラメーターを持つクラスまたはインターフェイスの宣言であり、 は の呼び出しです。ここで、 A1,...,An1inの場合、Ti は (ワイルドカードではなく) 型です。それで:C<T1,...,Tn>C

  • m を C のメンバ宣言またはコンストラクタ宣言とし、その宣言された型は T です。その場合、型 における m (§8.2、§8.8.6) の型C<T1,...,Tn>は ですT[A1 := T1, ..., An := Tn]
  • m を D のメンバーまたはコンストラクター宣言とします。ここで、D は C によって拡張されたクラスまたは C によって実装されたインターフェイスです。を D に対応するD<U1,...,Uk>のスーパータイプとします。その場合、 m inの型は m in の型です。C<T1,...,Tn>C<T1,...,Tn>D<U1,...,Uk>

パラメーター化された型への型引数のいずれかがワイルドカードである場合、そのメンバーとコンストラクターの型は未定義です。

つまり、によって宣言されたメソッドにFilter<File>は type がありboolean accept(File)ます。FileFilterメソッドも宣言しboolean accept(File)ます。

CombiningFilterFilterこれらの両方のメソッドを継承します。

どういう意味ですか?Java 言語仕様には次のように書かれています。

クラスは、オーバーライドと同等の (§8.4.2) シグネチャを持つ複数のメソッドを継承することができます。

クラス C が、C によって継承された別の具象メソッドのサブシグネチャである具象メソッドを継承すると、コンパイル時エラーになります。

(どちらの方法も具体的ではないため、これは当てはまりません。)

それ以外の場合は、次の 2 つのケースが考えられます。

  • 継承されたメソッドの 1 つが抽象メソッドでない場合、次の 2 つのサブケースがあります。
    • 抽象的でないメソッドが静的である場合、コンパイル時エラーが発生します。
    • それ以外の場合、abstract ではないメソッドは、それを継承するクラスに代わって他のすべてのメソッドをオーバーライドし、したがって実装すると見なされます。非抽象メソッドのシグネチャが他の継承されたメソッドのそれぞれのサブシグネチャでない場合、チェックされていない警告を発行する必要があります (抑制されていない限り (§9.6.1.5))。非抽象メソッドの戻り値の型が、他の継承されたメソッドのそれぞれに対して代入可能な戻り値の型 (§8.4.5) でない場合にも、コンパイル時エラーが発生します。非抽象メソッドの戻り値の型が、他の継承されたメソッドの戻り値の型のサブタイプでない場合は、チェックされていない警告を発行する必要があります。さらに、abstract ではない継承されたメソッドに、他の継承されたメソッドと競合する throws 句 (§8.4.6) がある場合、コンパイル時エラーが発生します。
  • 継承されたすべてのメソッドが抽象である場合、そのクラスは必然的に抽象クラスであり、すべての抽象メソッドを継承すると見なされます。このような継承された 2 つのメソッドのうち、一方のメソッドが他方のメソッドに代入可能な戻り値の型でない場合、コンパイル時エラーが発生します (この場合、throws 句はエラーを引き起こしません)。

したがって、オーバーライドと同等の継承されたメソッドの 1 つのメソッドへの「マージ」は、そのうちの 1 つが具体的である場合にのみ発生し、すべてが抽象的である場合、それらは別々のままであるため、それらすべてにアクセスでき、メソッド呼び出しに適用できます。

Java 言語仕様では、次に何が起こるかを次のように定義しています。

複数のメンバー メソッドがアクセス可能で、メソッド呼び出しに適用できる場合は、ランタイム メソッド ディスパッチの記述子を提供するために 1 つを選択する必要があります。Java プログラミング言語では、最も具体的な方法が選択されるという規則が使用されます。

非公式の直感は、最初のメソッドによって処理された呼び出しが、コンパイル時の型エラーなしで別のメソッドに渡される場合、あるメソッドが別のメソッドよりも具体的であるということです。

次に、より具体的に正式に定義します。定義は割愛しますが、各メソッドはそれ自体よりも具体的であるため、より具体的であることは半順序ではないことに注意してください。次に、次のように記述します。

m1 が m2 よりも具体的で、m2 がm1より具体的でない場合にのみ、メソッド m1 は別のメソッド m2 より厳密に具体的です。

したがって、同一の署名を持ついくつかのメソッドがある私たちの場合、それぞれが他よりも具体的ですが、どちらも厳密には他より具体的ではありません。

メソッドがアクセス可能で適用可能であり、適用可能でアクセス可能で厳密により具体的なメソッドが他にない場合、そのメソッドはメソッド呼び出しに対して最大限に固有であると言われます。

したがって、私たちの場合、継承されたすべてのacceptメソッドは最大限に固有です。

最大限に具体的なメソッドが 1 つだけある場合、そのメソッドは実際には最も具体的なメソッドです。これは、適用可能な他のアクセス可能な方法よりも必然的に具体的です。その後、§15.12.3 で説明されているように、さらにコンパイル時のチェックが行われます。

残念ながら、ここではそうではありません。

最も具体的なメソッドが 2 つ以上あるため、最も具体的なメソッドがない可能性があります。この場合:

  • すべての最大限に具体的なメソッドにオーバーライドと同等の (§8.4.2) 署名がある場合、次のようになります。
    • 最大限に具体的なメソッドの 1 つだけが抽象として宣言されていない場合、それは最も具体的なメソッドです。
    • それ以外の場合、すべての最大に固有のメソッドが抽象として宣言され、すべての最大に固有のメソッドの署名が同じ消去 (§4.6) を持つ場合、最も固有のメソッドは、最大に固有のメソッドのサブセットの中から任意に選択されます。最も具体的な戻り値の型。ただし、最も具体的なメソッドは、その例外またはその消去が最大限に具体的な各メソッドの throws 句で宣言されている場合にのみ、チェック済み例外をスローすると見なされます。
  • それ以外の場合、メソッド呼び出しがあいまいであると言い、コンパイル時エラーが発生します。

最後に、それが重要な点です。継承されたすべてのメソッドは同一であるため、オーバーライドと同等のシグネチャを持っています。ただし、ジェネリック インターフェイスから継承されたメソッドにはFilter、他のものと同じ消去はありません。

したがって、

  1. 最初の例は、すべてのメソッドが抽象的で、オーバーライドと同等であり、消去が同じであるため、コンパイルされます。
  2. 2 番目の例はコンパイルされません。これは、すべてのメソッドが抽象的でオーバーライドと同等ですが、それらの消去が同じではないためです。
  3. 3 番目の例はコンパイルされます。これは、すべての候補メソッドが抽象的で、オーバーライドと同等であり、消去が同じであるためです。(消去が異なるメソッドはサブクラスで宣言されているため、候補にはなりません)
  4. 4 番目の例はコンパイルされます。これは、すべてのメソッドが抽象的で、オーバーライドと同等であり、消去が同じであるためです。
  5. 最後の例 (CombiningFileFilter で抽象メソッドを繰り返す) はコンパイルされます。これは、そのメソッドが継承されたすべてのメソッドとオーバーライド等価であるacceptため、それらをオーバーライドするためです (オーバーライドには同じ消去が必要ないことに注意してください!)。したがって、適用可能でアクセス可能な方法は 1 つしかなく、したがって最も具体的な方法です。

仕様がオーバーライド等価性に加えて同じ消去を必要とする理由を推測することしかできません。これは、非ジェネリック コードとの下位互換性を維持するために、メソッド宣言が型パラメーターを参照するときに、署名が消去された合成メソッドをコンパイラが発行する必要があるためである可能性があります。この消去された世界で、コンパイラーがメソッド呼び出し式のターゲットとして使用できるメソッドは何ですか? Java 言語仕様では、適切な、共有された、消去されたメソッド宣言が存在することを要求することで、この問題を回避しています。

結論として、javac の動作は直感的とは言えませんが、Java 言語仕様によって義務付けられており、Eclipse は互換性テストに失敗しました。

于 2012-01-23T03:45:39.733 に答える