<の違いは何ですか?Baseを拡張し、TがBaseを拡張しますか?


29

この例では:

import java.util.*;

public class Example {
    static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    static void function(List<? extends Number> outer)
    {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

doesntCompile() でコンパイルできません:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

while compiles()はコンパイラーによって受け入れられます。

この回答は、唯一の違いはとは異なり<? ...><T ...>後で型を参照できることですが、そうではないようです。

違いは何である<? extends Number><T extends Number>、この場合、なぜ最初のコンパイルはしないのですか?


コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
Samuel Liew

[1] Javaジェネリック型:リストの違い<?extends Number>とList <T extends Number>は同じ質問をしているように見えますが、興味深いかもしれませんが、実際には重複していません。[2]これは良い質問ですが、タイトルは最終文で尋ねられる特定の質問を適切に反映していません。
skomisa

これはすでにここで回答されていますJava Generic List <List <?拡張ナンバー>>
MạnhQuyếtグエン

ここの説明どうですか?
jrook

回答:


14

次のシグネチャでメソッドを定義することにより:

static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

そしてそれを次のように呼び出します:

compiles(new HashMap<Integer, List<Integer>>());

jls§8.1.2で、次のことがわかります(私が太字にした興味深い部分)。

ジェネリッククラス宣言は、型引数による型パラメーターセクションの可能な呼び出しごと1つずつ、パラメーター化された型のセット(4.5)を定義します。これらのパラメーター化された型はすべて、実行時に同じクラスを共有します。

つまり、タイプTは入力タイプと照合され、割り当てられIntegerます。署名は実質的にになりstatic void compiles(Map<Integer, List<Integer>> map)ます。

それがに来るときdoesntCompile方法、JLSは、(サブタイプのルールを定義し§4.5.1私が太字に、):

型引数T1は、T2で示される型のセットが以下の規則の再帰的および推移的閉包の下でT1で示される型のセットのサブセットである場合、T2 <= T1と記述された別の型引数T2を含むと言います(ここで、<:はサブタイプを示します(§4.10)):

  • ?T <=?を拡張する T <:Sの場合、Sを拡張します

  • ?T <=?を拡張する

  • ?スーパーT <=?S <:Tの場合、スーパーS

  • ?スーパーT <=?

  • ?スーパーT <=?オブジェクトを拡張する

  • T <= T

  • T <=?Tを拡張する

  • T <=?スーパーT

つまり、? extends Number実際にはが含まれIntegerList<? extends Number>いるList<Integer>、または含まれていることもありますが、Map<Integer, List<? extends Number>>andには当てはまりませんMap<Integer, List<Integer>>。このトピックの詳細については、このSOスレッドをご覧ください。?宣言することで、ワイルドカードを使用したバージョンを引き続き機能させることができますList<? extends Number>

public class Example {
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
    static <T extends Number> void compiles(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    }
}

[1] ? extends Numberではなく、という意味だと思います? extends Numeric。[2] 「List <?extends Number>およびList <Integer>はそうではないというあなたの主張は正しくありません。@VinceEmighがすでに指摘しstatic void demo(List<? extends Number> lst) { }たように、メソッドを作成してthis demo(new ArrayList<Integer>());またはthisのように呼び出すことができdemo(new ArrayList<Float>());、コードはコンパイルされて正常に実行されます。それとも、あなたが言ったことを誤解しているのか、誤解しているのでしょうか?
skomisa

@あなたはどちらの場合も正しい。2つ目のポイントについては、誤解を招くような方法で書きました。List<? extends Number>それ自体ではなく、マップ全体のタイプパラメータを意味しました。コメントありがとうございます。
Andronicus

同じ理由から@skomisa List<Number>は含まれていませんList<Integer>。関数があるとしますstatic void check(List<Number> numbers) {}。呼び出すときcheck(new ArrayList<Integer>());、それはコンパイルされません、あなたにメソッドを定義する必要がありますstatic void check(List<? extends Number> numbers) {}。マップの場合も同じですが、ネストが増えています。
Andronicus

1
@skomisa Numberは、リストの型パラメーターと同様に? extends、それを共変にするために追加する必要があります。これList<? extends Number>は、型パラメーターでMapあり? extends、共分散も必要です。
Andronicus

1
OK。マルチレベルのワイルドカード(別名「ネストされたワイルドカード」)のソリューションを提供し、関連するJLSリファレンスにリンクしているので、報奨金があります。
スコミサ

6

通話中:

compiles(new HashMap<Integer, List<Integer>>());

TはIntegerに一致するため、引数の型はMap<Integer,List<Integer>>です。メソッドの場合はそうではありません。doesntCompile引数の型はMap<Integer, List<? extends Number>>、呼び出しの実際の引数が何であっても変わりません。そして、それはから割り当て可能ではありませんHashMap<Integer, List<Integer>>

更新

このdoesntCompileメソッドでは、次のようなことを妨げるものは何もありません。

static void doesntCompile(Map<Integer, List<? extends Number>> map) {
    map.put(1, new ArrayList<Double>());
}

したがって、明らかに、それはa HashMap<Integer, List<Integer>>を引数として受け入れることができません。


doesntCompileそれでは、有効な呼び出しはどのようになるでしょうか?それについて興味があるだけです。
Xtreme Biker

1
@XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());は、同様に機能しdoesntCompile(new HashMap<>());ます。
skomisa

@XtremeBiker、これでも機能します、Map <Integer、List <?Number >>を拡張します。map = new HashMap <Integer、List <?Number >>();を拡張します。map.put(null、new ArrayList <Integer>()); doestCompile(map); doestCompile(map);
モンキー

「それから割り当て可能ではありませんHashMap<Integer, List<Integer>>」なぜそれから割り当て可能ではないのか詳しく説明していただけませんか?
Dev Null

@DevNull上記の私の更新を参照してください
モーリスペリー

2

デモのシミュレーション例。同じ例を以下のように視覚化できます。

static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}

List<Pair<? extends Number>>はマルチレベルのワイルドカードタイプList<? extends Number>ですが、は標準のワイルドカードタイプです。

ワイルドカードタイプの有効な具体的なインスタンス化には、およびそのサブタイプがList<? extends Number>含まれますが、その場合、タイプ引数のタイプ引数であり、それ自体がジェネリックタイプの具体的なインスタンス化を持ちます。NumberNumberList<Pair<? extends Number>>

ジェネリックスは不変なので、Pair<? extends Number>ワイルドカードタイプはのみを受け入れることができPair<? extends Number>>ます。内部タイプ? extends Numberはすでに共変です。共分散を可能にするには、囲んでいる型を共変として作成する必要があります。


どのようにそれはそれで<Pair<Integer>>動作しません<Pair<? extends Number>>が、と仕事をしますか<T extends Number> <Pair<T>>
jaco0646

@ jaco0646あなたは本質的にOPと同じ質問をしていて、Andronicusからの回答が受け入れられました。その答えのコード例を参照してください。
skomisa

@skomisa、はい、私はいくつかの理由で同じ質問をしています。1つは、この回答が実際にはOPの質問に対応していないように見えることです。2つ目は、この答えを理解しやすいということです。Andronicusからの回答を、ネストされたジェネリックとネストされていないジェネリック、さらにはTvs を理解するような形で理解することはできません?。問題の一部は、Andronicusが彼の説明の重要なポイントに達したときに、些細な例だけを使用する別のスレッドに頼ることです。私はここでより明確でより完全な答えを得ることを望んでいました。
jaco0646

1
@ jaco0646 OK。Angelika Langerによるドキュメント「Java Generics FAQs-Type Arguments」には、マルチレベル(つまり、ネストされた)ワイルドカードはどういう意味ですかというFAQがあります。。これは、OPの質問で提起された問題を説明するために私が知っている最良の情報源です。ネストされたワイルドカードのルールは、単純でも直感的でもありません。
skomisa

1

一般的なワイルドカードのドキュメント、特にワイルドカードの使用に関するガイドラインを参照することをお勧めします

率直に言ってあなたのメソッド#doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) {}

のように電話します

doesntCompile(new HashMap<Integer, List<Integer>>());

基本的に正しくない

法的な実装を追加しましょう:

    static void doesntCompile(Map<Integer, List<? extends Number>> map) {
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    }

ダブルナンバーを拡張するため、プットはので、それは、細かい本当にList<Double>絶対に罰金などでList<Integer>、右?

しかし、あなたの例からここを渡すことが合法だとまだ思いnew HashMap<Integer, List<Integer>>()ますか?

コンパイラはそうは思わず、そのような状況を回避するために彼(その?)を最善の方法で実行しています。

メソッド#compileを使用して同じ実装を実行してみてください。コンパイラーでは、明らかにdoubleのリストをマップに入れることができません。

    static <T extends Number> void compiles(Map<Integer, List<T>> map) {
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    }

基本的には何も置くことができませんがList<T>new HashMap<Integer, List<Integer>>()or new HashMap<Integer, List<Double>>()またはnew HashMap<Integer, List<Long>>()orでそのメソッドを呼び出すのが安全なのはそのためnew HashMap<Integer, List<Number>>()です。

つまり、一言で言えば、コンパイラで不正行為を行おうとしているのであり、そのような不正行為からかなり守ります。

注意:モーリス・ペリーが投稿した答えは完全に正しいです。私はそれが十分に明確であるかどうかわからないので、より広範な投稿を追加しようとしました(本当に私がなんとか願っています)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.