2016-11-14 7 views
3

ワイルドカードのパラメータ化された型を返すコードがあります。これをパラメトリックメソッドに渡そうとしていますが、コンパイラエラーが発生します。誰かがなぜタイプが一致しないのかを私に説明することができますか?これを解決する最も良い方法は何ですか?Java 2レベルのパラメータ化された型の推論

static <L extends List<T>,T extends Number> 
void useList(List<L> list) {} 

public static void main(String[] args) { 
    List<List<? extends Number>> list = null; 
    useList(list); 
} 

コンパイルエラー:私の実際のコードlist

demo/GenericsHell.java:75: error: method useList in class GenericsHell cannot be applied to given types; 
     useList(list); 
     ^
    required: List<L> 
    found: List<List<? extends Number>> 
    reason: inference variable L has incompatible bounds 
    equality constraints: List<? extends Number> 
    upper bounds: List<CAP#1> 
    where L,T are type-variables: 
    L extends List<T> declared in method <L,T>useList(List<L>) 
    T extends Number declared in method <L,T>useList(List<L>) 
    where CAP#1 is a fresh type-variable: 
    CAP#1 extends Number from capture of ? extends Number 
1 error 

は、「リスト」(実際にはカスタムジェネリッククラス)のいくつかのタイプを返すことができ、複雑な方法によって生成されています。タイプセーフな方法でこれを受け入れるように、useList関数をパラメータ化する別の方法がありますか?


編集1

私は簡潔で、コヒーレント質問へのコードの大規模なモジュールを軽減しようとしています。答えから私は上記のコードが私が持っている正確な問題を単純化し過ぎていることがわかります。もっと複雑な例を挙げてみようと思いますが、私の制約を明確にしています。

まず、私は実際にリストを使用しているのではなく、二重のパラメータ化を使用するより複雑なクラスです。私はこれを行う任意の標準クラスを考えることはできませんので、私は例で使用するものを定義します:

static class NumberLists<L extends List<T>,T extends Number> extends ArrayList<L> {} 

私は複数回のタイプを再利用避けるだろう、それはレベルを維持する方が簡単ですまっすぐ。 useList()メソッドは、実際にそれが内部的に両方のタイプを使用しているため、二重のパラメータ化を必要とします:

static <L extends List<T>,T extends Number> 
Set<NumberLists<L,T>> useList(L list) { 
    NumberLists<L,T> nl = new NumberLists<L, T>(); 
    nl.add(list); 
    return Collections.singleton(nl); 
} 

このフレームワークは、限り、あなたは具体的なクラスを持っているように素晴らしい作品:

// Everything works with a concrete class 
    List<Integer> intList = new ArrayList<Integer>(); 

    // Use with parametric functions 
    Set<NumberLists<List<Integer>,Integer>> concreteSet = useList(intList); 
    // Access & use elements 
    NumberLists<List<Integer>,Integer> concreteNL = concreteSet.iterator().next(); 
    concreteNL.add(intList); 

問題はそのIであります

static List<? extends Number> makeList() { 
    if(Math.random()<.5) { 
     return new ArrayList<Integer>(); 
    } else { 
     return new ArrayList<Double>(); 
    } 
} 

List<? extends Number> numList = makeList(); 

これは、上記で可能な多くのパターンを壊します。

// What is the type here? This is obviously an error 
    Set<NumberLists<? extends List<? extends Number>>,? extends Number> paramSet = useList(numList); 
    // Type should ideally be compatible with numList still 
    NumberLists<?,?> paraNL1 = paramSet.iterator().next(); 
    // Captures don't match 
    paraNL1.add(numList); 

したがって、コンパイル時にnumListの具体的なクラスを知ることができないという問題があります。私はnumListの型がuseListによって返される型と一致しなければならないことを知っているので、実際に型が安全でないものは何もしていないように感じます。

私はコードのほとんどの部分の型署名を管理しています。しかし、私は次のことを好むだろう:

  • 、また、型が実行時まで、具体的に知ることができない入力を受け取ることになります具象クラス(例えばintList)
  • とうまくやタイプセーフな方法で動作するはずです
  • キャストまたはその他のチェックされていない操作が実行されると、入力の構成の近くで発生するはずです。以下の操作をチェックする必要があります。

完全なコンパイル以外のJavaファイルについては、https://gist.github.com/sbliven/f7babb729e0b1bee8d2dabe5ee979431を参照してください。私がこの質問に興味がある理由は、https://github.com/biojava/biojava/issues/354を参照してください。

+1

あなたのリストにはリストがあり、 'useList()' expectリストには 'List'ではなく' Number'が含まれています。 – markspace

+0

@markspaceトップコードブロックでは、LにList <をバインドすることを望んでいました。 Number>を拡張します。私はそれがレベルの正しい数だと思う。 – Quantum7

+0

質問を更新し、ネストされたリストの使用を避けました。うまくいけば、タイプシグネチャの混乱を少なくします。 – Quantum7

答えて

2

、あなたの問題はあなたには、いくつかの変数に代入しようとしているという事実から来ていますその型は一致しません。あなたはこのコードをチェックすると

List<? extends Number> numList = makeList(); 
useList(numList); 

が、それはすでにコンパイル:

最も簡単な方法は、この単純なコードがコンパイルされることを確認するためにuseList()呼び出しから開始することです。

結果をローカル変数に割り当てるようにIDEに依頼してください。 IDEは、変数を宣言するための式のタイプを計算し、ほらます:

Set<? extends NumberLists<? extends List<? extends Number>, ? extends Number>> paramSet = 
    useList(numList); 

(、物事に多くを簡素化しますNumbersListLタイプ、取り除くことができた場合)

paraNL1.add(numList);コールでは、ワイルドカードのために動作しません。コンパイラは、不明なタイプのリストを受け入れることを確認する方法がありません。

あなたは同じプロセスに従うことによって、宣言を修正した場合でも:

NumberLists<? extends List<? extends Number>, ? extends Number> paraNL1 = 
    paramSet.iterator().next(); 

paraNL1.add(numList); // will still not compile 

あなたは、消費者(それがnumListを消費)としてparaNL1を使用しようとしていることがわかり、そしてPECSは、あなたがそれを宣言しなければならないことを示していますそのためにはsuperが必要です。

実際、コンパイラがあなたのNumberListsためのいくつかのタイプL extends List<? extends Number>が存在しなければならないことを知っているが、それはそれを知らないと、それはnumListのタイプと一致していることを確認する方法はありません。 は、例えば、LinkedList<Float>であり、numListは、ArrayList<Integer>であり得る。

編集:あなたはこのようなwilcard capture helper methodを使用することができるかもしれない、それを動作させるために:

private static <T extends Number, L extends List<T>> void helper(L numList) { 
    Set<NumberLists<L, T>> paramSet = useList(numList); 
    NumberLists<L, T> paraNL1 = paramSet.iterator().next(); 
    paraNL1.add(numList); 
} 

(これも、あなたは Set宣言で ? extends NumerListsを取り除くことができます)

とそれを呼び出す

List<? extends Number> numList = makeList(); 
helper(numList); 
+0

私はより良いIDE-Eclipse Lunaが 'Set 'を提案する必要があると思います。では、スーパークラスを使用してクラスを再定義して、このコードが機能するようにする方法はありますか?私はそれが私の実際のコードで可能かどうかはわかりませんが、それは私にいくつかのアイデアを与えるかもしれません。 – Quantum7

+0

ルナは2歳以上です。型推論はJava 8と大きく変わり、IDEのサポートが大幅に改善されました。物事をコンパイルするのが最も簡単なのは、ワイルドカードの使用を避けることです。たぶん[ワイルドカードキャプチャヘルパーメソッド](http://stackoverflow.com/questions/30763895/why-use-a-wild-card-capture-helper-method)を使用してください。 –

+0

@ Quantum7ワイルドカードキャプチャヘルパーメソッドを使用した例を追加しました –

2

コンパイラが具体的な型をTに割り当てたいというのは問題です。あなたの宣言でTlistの宣言に従って? extends Numberになる必要があります。これは具体的ではありません。

あなたは次の宣言のいずれかにuseListを変更することができますし、コンパイラが幸せになります:

static <L extends List<? extends Number>> void useList(List<L> list) { 
} 

(ノーT、問題ありません)、または

static <L extends List<? extends T>, T extends Number> void useList(List<L> list) { 
} 

(ここでTNumberになります) 。あなたは、変数宣言で? extends Numberを置き換えることができれば

、すべてがあまりにも、罰金になります:最初のコンパイルの問題については

public static <T extends Number> void main(String[] args) { 
    List<List<T>> list = null; 
    useList(list); 
} 
+0

これは私が探していたものへの一歩だと思いますが、これらのオプションのどれも私には適していないようです。私は私の制約をより明確に説明するために私のオリジナルの質問を編集しました。 – Quantum7