消去はJavaのオーバーライドシナリオをどのように処理しますか?

how does erasure handles overriding scenarios in Java?


質問 written by Community @2017-05-23 10:33:22Z

: 6 : 2 : 215

この質問は、これまでの私の投稿からのものです。 質問を投稿する前に、Oracleドキュメントの内容を貼り付けています。

8.4.8.1. Overriding (by Instance Methods)

An instance method m1, declared in class C, overrides another instance method m2, declared in class A iff all of the following are true:

    C is a subclass of A.

    The signature of m1 is a subsignature (§8.4.2) of the signature of m2.

8.4.2. Method Signature 
The signature of a method m1 is a subsignature of the signature of a method m2 if either:

    m2 has the same signature as m1, or

    the signature of m1 is the same as the erasure (§4.6) of the signature of m2.

overridingが関係する場合のtype erasure私の理解は次のとおりです。 erasuresignature of m1 and m2が同じ場合、それらはoverriddenれたと見なされます。 上記の前回の投稿で、 List<String>をとるparent class methodを、 type erasure後に残ったものがList<Object>だけであると仮定して、 List<Integer>をとるsubclass methodoverrideしようとしました。 しかし、それは間違っています。 したがって、 erasureが関係する場合のメソッドoverriding上記の定義を理解することはまったく間違っています。 上記の点を説明する簡単な例を挙げてください。

ありがとう。 ところで、上記のポイントはここから来ています。

回答 1 written by rgettman @2014-02-11 00:13:18Z
3

メソッドが別のメソッドをオーバーライドするかどうかは、メソッドの消去を処理するだけではありません。 コンパイラは、メソッドが別のメソッドをオーバーライドするかどうかを判断し、型消去が発生する前に、関連する汎用型パラメーターにアクセスします。

消去を使用してオーバーライドを決定することについてのあなたの考えは、まったく正しくありません。 次のJLSセクション8.4.8.1をディスカッションに追加しましょう。

クラスCで宣言されたインスタンスメソッドm1は、次のすべてが真である場合、クラスAで宣言された別のインスタンスメソッドm2をオーバーライドします。

  • CはAのサブクラスです。

  • m1の署名は、m2の署名のサブ署名(§8.4.2)です。

  • どちらか:

    • m2は、パブリック、保護、またはCと同じパッケージ内のデフォルトアクセスで宣言されている、または

    • m1はメソッドm3(m3はm1とは異なり、m3はm2とは異なる)をオーバーライドし、m3はm2をオーバーライドします。

m1m2サブシグニチャであることが必要ですが、その逆ではありません。

例1:

class A {
   public void foo(List<String> list) { }
}

class B extends A {
   @Override
   public void foo(List list) {}
}

Bfooメソッドの署名はBfooメソッドの消去と同じであるため、これは正当です。

例2:

Example 2:

これは合法です。なぜなら、署名は(生であっても)同じだからです。

例3:

This is legal, because the signatures are the same (even if they are raw).

オーバーライドするメソッドの消去は考慮されないため、これは違法です。 つまり、 List<Integer>Listの消去と比較されます。これは、まだListであり、同じではありません。

例4:

class A {
   public void foo(List list) { }
}

class B extends A {
   @Override
   public void foo(List<Integer> list) {}
}

オーバーライドするメソッドの消去は考慮されないため、これは再び合法ではありません。 つまり、 List<Integer>List<String>List )の消去と比較され、それらは同じではありません。

オーバーライドメソッドのパラメーターのジェネリック型パラメーターを変更することはできません(たとえば、 List<String>List<Integer>することはできません。ジェネリックを使用しないメソッドをオーバーライドするとき、ジェネリックを導入することはできませんただし、オーバーライドするときにジェネリックを削除できます(例: List<String> to List )。

コメント 1

親クラスでfoo(List <?extends Number>)を使用し、サブクラスでfoo(List <Integer>)を使用するのはどうですか。これは許可されていませんが、なぜそうなのですか?ところで、あなたの説明は素晴らしいです。

written by ブレインストーム14 @2014-02-11 00:47:59Z

コメント 2

親クラスのメソッドにList<? extends Number>List<? extends Number>List<Number>を引数として受け入れる必要があります。子クラスにList<Integer>がある場合、 List<Number>を引数として受け入れることはできません。しかし、オーバーライドするには、オーバーライドされたメソッドが受け入れるものと同じものを受け入れなければなりません。子メソッドは、パラメーターとしてList<Number>持つことさえできません。これは、スーパークラスが受け入れる引数としてList<Integer>を許可しないためです。したがって、オーバーライドするメソッドのシグネチャはList<Number>またはList<Integer>はできません。一致する必要がありList<? extends Number>List<? extends Number> )。

written by リッジマン14 @2014-02-11 00:52:38Z

コメント 3

親クラスは、 NumberまたはDouble, Integerなどのextends Numberextends Numberものを受け入れますか?子クラスはList<Number> as parameterList<Number> as parameter overrideできません。これは、単にListsinvariantためですか??

written by ブレインストーム14 @2014-02-11 01:51:58Z

コメント 4

そのとおりです;子クラスのメソッドはList<? extends Number>また、スーパークラスメソッドが受け入れるすべてのもの、たとえばList<Number>List<Integer>List<Double>も受け入れる必要があるため、 List<? extends Number>ます。Javaジェネリックは不変であり、 Listはジェネリックを使用します。

written by リッジマン14 @2014-02-11 01:59:46Z

コメント 5

例4では、 @override 、コンパイルエラーが発生します。class B inherits foo(List<String>) from A 、独自のfoo(List<Integer>) method持っているため、これはoverloadingれるはずだと思いました。type erasure原因で、生の型foo (List)が得られた場合、それをcastバックするための型情報はどこに格納されていますか?

written by ブレインストーム @2014-02-11 02:14:31Z

回答 2 written by Community @2017-05-23 12:10:53Z
2

Javaは厳密に型指定された言語であるため、共分散と型の連携方法に注意する必要があります。 消去は、型の規則を破る言い訳ではありません。

例えば:

class BaseGood<T> {
    public void doStuff(T elem) {
                // ...
    }
}
class DerivedGood<T> extends BaseGood {
    public void doStuff(Object elem) {
        super.doStuff(elem);
    }
}
class BaseBad {
    public void doStuff(List<Double> list) {
        // ...
    }
}
class DerivedBad extends BaseBad {
    public void doStuff(List<Integer> list) {
        super.doStuff(list);
        // ...
    }
}

ここで、消去には2つの異なるケースがあります。

DerivedGoodクラスとDerivedGoodクラスでは、2つのメソッドの消去は同じです: void doStuff(Object elem) Tは常にObject型であるため、関数は型セーフであることがvoid doStuff(Object elem)ます。

DerivedBadクラスとDerivedBadクラスでは、2つのメソッドの消去は同じです: void doStuff(List list) ; ただし、型システムはList<Integer>からList<Double>安全に変換できません(または、その点でListとの間で安全に変換できません)。 この変換を許可すると、潜在的にDoubleIntegerのリストに入れようとする可能性があります(またはジェネリックが検出をコンパイル時に移動する問題)。

消去後にメソッドをFriendlyにオーバーライドしても、型システムが消去前の非互換性をチェックできないわけではありません。

編集

また、クラス/メソッド定義に適用される実際の消去は、コンパイル時には発生せず、実行時に発生します。 ジェネリックメソッドを参照するコードは、関数がジェネリックなしで呼び出されたかのようにコンパイルされ、コンパイラとランタイムチェックによって型安全性が強制されます。

参照: Javaジェネリック-型消去-いつ何が起こるか

型消去されたジェネリッククラスをオーバーライドに渡す別のケースもあります。型システムはメソッドの型に基づいて適切なリストを渡したかどうかをチェックできないため、それを許可する必要があります。

コメント 1

わかりました、私はあなたの説明に従っていると思います。上記のdouble[]int[]してみてください。 arrayscovariantので、問題はないはずですよね?

written by ブレインストーム14 @2014-02-11 02:04:19Z

コメント 2

doubleintは継承階層にないため、これは機能しません。intintスーパーセットでもサブセットでもありません。ただし、 A[]DerivedFromA[] A[]でオーバーライドすると、型システムがDerivedFromA[]すべての要素がAなることを保証できるため、コンパイルされます。ただし、 A[]内に別のクラスを保存しようとすると、 ClassCastException (基本的に、型チェックをランタイムに移動します)。

written by hsun324 14 @2014-02-11 04:14:54Z