web-dev-qa-db-ja.com

Javaタイプパラメータに下限を設定できないのはなぜですか?

Javaジェネリック型パラメーターを下限にバインドすることはできません(つまり、superキーワードを使用します)。 Angelika Langer Generics = FAQこの件について言わなければなりませんでした 。彼らはそれが基本的に役に立たない(「意味をなさない」)下限に帰着すると言います。

私は確信していません。型付きの結果を生成するライブラリメソッドの呼び出し元に対してより柔軟に対応できるようにするための使用法を想像できます。ユーザー指定のサイズの配列リストを作成し、空の文字列で埋めるメソッドを想像してみてください。簡単な宣言は

public static ArrayList<String> createArrayListFullOfEmptyStrings(int i);

しかし、それはあなたのクライアントに不必要に制限的です。なぜ彼らはあなたのメソッドをこのように呼び出すことができないのですか?

//should compile
List<Object> l1 = createArrayListFullOfEmptyStrings(5); 
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
List<String> l3 = createArrayListFullOfEmptyStrings(5);

//shouldn't compile
List<Integer> l4 = createArrayListFullOfEmptyStrings(5);

この時点で、次の定義を試してみたくなるでしょう。

public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
  List<T> list = new ArrayList<T>(size);
  for(int i = 0; i < size; i++) {
     list.add("");
  }
  return list;
}

ただし、コンパイルは行われません。このコンテキストでは、superキーワードは無効です。

私の例は悪い例の上にありますか(以下のことを無視して)?ここで下限が役に立たないのはなぜですか?そして、それが役立つとしたら、Javaで許可されていない本当の理由は何ですか?

P.S.

私はより良い組織がかもしれない次のようなものであることを知っています:

public static void populateListWithEmptyStrings(List<? super String> list, int size);

List<CharSequence> list = new ArrayList<CharSequence>();
populateListWithEmptyStrings(list, 5);

この質問の目的のために、要件のために、1つのメソッド呼び出しで両方の操作を実行する必要があると仮定できますか?

編集

@Tom Gは(当然のことながら)List<CharSequence>List<String>。 1つは、返されたリストが不変であるとは誰も言わなかったため、次の1つの利点があります。

List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
l2.add(new StringBuilder("foo").append("bar"));
50
Mark Peters

基本的に、それは十分に有用ではありません。

あなたの例は、下限の唯一の利点、FAQ呼び出しRestricted Instantiation

肝心なのは:「スーパー」バウンドがあなたを買う制限 Numberのスーパータイプのみをtype引数として使用できます。 ..。

しかし、他の投稿が指摘しているように、この機能でさえも有用性が制限される可能性があります。

ポリモーフィズムと特殊化の性質により、FAQ(非静的メンバーへのアクセスで説明されているように、上限は下限よりもはるかに便利です。 )および型消去)。下限によって導入された複雑さは、その制限された値の価値がないと思います。


OP:付け加えたいのですが、あなたはそれが有用であることを示したと思いますが、十分に有用ではありません。反駁できないキラーのユースケースを考え出し、JSRを支援します。 :-)

15
Bert F

仕様では、たとえば、型パラメーターの下限について説明しています。

4.10.2

型変数は、その下限の直接のスーパータイプです。

5.1.10

新しい型変数...その下限

ワイルドカードキャプチャの結果として合成変数である場合、型変数には(null以外の)下限があるように見えます。言語がすべての型パラメーターの下限を許可している場合はどうなりますか?おそらくそれほど問題にはならず、ジェネリックスを単純化するためだけに除外されています(まあ...)更新下限型パラメーターの理論的調査は徹底的に行われていないと言われています。

更新:下限は問題ないと主張する論文:DanielSmithによる「Java型推論が壊れています:修正できますか」

RETRACT:次の引数は間違っています。 OPの例は正当です。

あなたの特定の例はあまり説得力がありません。まず、タイプセーフではありません。返されるリストは確かにList<String>、それを別のタイプとして見るのは安全ではありません。コードがコンパイルされると仮定します。

    List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

次に、文字列以外を追加できますが、これは間違っています

    CharSequence chars = new StringBuilder();
    l2.add(chars); 

まあList<String>はそうではありませんが、CharSequenceのリストのようなものです。ワイルドカードを使用すると、ニーズを解決できます。

public static  List<String> createArrayListFullOfEmptyStrings(int size)  

// a list of some specific subtype of CharSequence 
List<? extends CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

// legal. can retrieve elements as CharSequence
CharSequence chars = l2.get(0);

// illegal, won't compile. cannot insert elements as CharSequence
l2.add(new StringBuilder());
11
irreputable

答え以上に、これは別の(おそらくキラー?)ユースケースです。 ModelDecoratorヘルパーがあります。次のパブリックAPIが必要です

class ModelDecorator<T>{
    public static <T> ModelDecorator<T> create(Class<T> clazz);
    public <SUPER> T from(SUPER fromInstance);
}

したがって、クラスA、BがAを拡張すると、次のように使用できます。

A a = new A();
B b = ModelDecorator.create(B.class).from(a);

ただし、TとSUPERに境界を設定したいので、APIを使用してインスタンス化できるのはサブクラスのみであることを確認します。現時点では、次のことができます。

C c = new C();
B b = ModelDecorator.create(B.class).from(c);

BがCから継承しない場合。

明らかに、私ができるなら:

    public <SUPER super T> T from(SUPER fromInstance);

それは私の問題を解決するでしょう。

1
Jason Oviedo

その時点でリストを入力するとどのような利点がありますか?返されたコレクションを反復処理する場合でも、次のことができるはずです。

for(String s : returnedList) {
CharSequence cs = s;
//do something with your CharSequence
}
0
Tom G

編集:私は良いニュースをもたらします。あなたが望むもののほとんどを手に入れる方法があります。

public static <R extends List<? super String>> R createListFullOfEmptyString(IntFunction<R> creator, int size)
{
  R list = creator.apply(size);
  for (int i = 0; i < size; i++)
  {
    list.add("");
  }
  return list;
}

// compiles
List<Object> l1 = createListFullOfEmptyString(ArrayList::new, 5);
List<CharSequence> l2 = createListFullOfEmptyString(ArrayList::new, 5);
List<String> l3 = createListFullOfEmptyString(ArrayList::new, 5);
// doesn't compile
List<Integer> l4 = createListFullOfEmptyString(ArrayList::new, 5);

欠点は、クライアントが変更するRのインスタンス、またはRを構築するための何らかの手段を提供する必要があることです。安全に構築する他の方法はありません。

情報提供の目的で、以下の元の回答を保持します。


要約すれば:

正当な理由はありません、それはただ行われていません。

そして、そのような時まで、以下のすべてを行うメソッドに対して、正しい分散で正確な型を書くことは不可能です。

A)パラメータ化されたデータ構造を受け入れるか作成する

B)計算された(渡されていない)値をそのデータ構造に書き込みます

C)そのデータ構造を返す

値の書き込み/受け入れは、共変性が適用される場合とまったく同じです。つまり、データ構造のtypeパラメーターは、データ構造に書き込まれる値のタイプによって下限を指定する必要があります。 Javaでそれを表現する唯一の方法は、現在、データ構造に下限のワイルドカードを使用することです(例:List <?super T>)。


OPなどのAPIを設計している場合、これは自然に(ただし法的にではなく)次のように表現される可能性があります。

// T is the type of the value(s) being computed and written to the data structure

// Method creates the data structure
<S super T> Container<S> create()

// Method writes to the data structure
<S super T> Container<S> write(Container<S> container)

次に、利用可能なオプションは次のとおりです。

A)下限のワイルドカードを使用し、呼び出し元に出力をキャストするように強制します。

// This one is actually useless - there is no type the caller can cast to that is both read- and write-safe.
Container<? super T> create()

// Caller must cast result to the same type they passed in.
Container<? super T> write(Container<? super T> container)

B)書き込まれる値のタイプに一致するようにデータ構造のtypeパラメーターを過度に制限し、呼び出し元に入力および出力をキャストするように強制します。

// Caller must accept as-is; cannot write values of type S (S super T) into the result.
Container<T> create()

// Caller must cast Container<S> (S super T) to Container<T> before calling, then cast the result back to Container<S>.
Container<T> write(Container<T> container)

C)新しいタイプのパラメーターを使用し、内部で独自の安全でないキャストを実行します。

// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> create()

// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> write(Container<S> container)

あなたの毒を選んでください。

0
Daniel Avery