<T> List<? extends T> f()
は便利な署名ですか?何か問題がありますか?
これはインタビューの質問でした。私はこれを知っている:
List<? extends Number> lst = obj.<Number>f()
のように使用して、次に、これらを含まないListメソッドのみを呼び出すことができます[〜#〜] t [〜#〜] =シグネチャ内(たとえば、isEmpty()、size()、ではないadd(T)、remove(T)これで問題は完全に解決しましたか?
このメソッドシグネチャは "便利"です。これは、重要な、縮退していないメソッドを実装できるという意味で(つまり、null
を返し、スローすることで)エラーだけが選択肢ではありません)。次の例に示すように、このような方法は、たとえば、次のような代数的構造を実装するのに役立ちます。モノイド。
まず、List<? extends T>
は、次のプロパティを持つタイプです。
T
型に準拠していることがわかっているため、このリストから要素を抽出するときはいつでも、T
が期待される位置で使用できます。あなたはこのリストから読み取ることができます。T
の特定のサブタイプのインスタンスをこのリストに追加できるかどうかは確実ではありません。つまり、効果的にそのようなリストに新しい要素を追加することはできません(null
s /型キャスト/ Javaの型の不健全性を利用しない限り)システム、つまり)。組み合わせて、それはList<? extends T>
は、タイプレベルの追加保護を備えた追加保護リストのようなものです。
このような「追加保護」リストを使用すると、実際に意味のある計算を行うことができます。以下にいくつかの例を示します。
単一要素で追加保護リストを作成できます:
public static <T> List<? extends T> pure(T t) {
List<T> result = new LinkedList<T>();
result.add(t);
return result;
}
通常のリストから追加保護リストを作成できます。
public static <T> List<? extends T> toAppendProtected(List<T> original) {
List<T> result = new LinkedList<T>();
result.addAll(original);
return result;
}
追加保護リストを組み合わせることができます。
public static <T> List<? extends T> combineAppendProtected(
List<? extends T> a,
List<? extends T> b
) {
List<T> result = new LinkedList<T>();
result.addAll(a);
result.addAll(b);
return result;
}
そして、この質問で最も重要なのは、特定のタイプの空の追加保護リストを返すメソッドを実装できることです。
public static <T> List<? extends T> emptyAppendProtected() {
return new LinkedList<T>();
}
一緒にcombine
とempty
は実際の代数構造(モノイド)を形成し、pure
のようなメソッドは非縮退であることを保証します(つまり、空のリスト)。実際、通常の Monoid typeclassに似たインターフェースがある場合:
public static interface Monoid<X> {
X empty();
X combine(X a, X b);
}
次に、上記のメソッドを使用して、次のように実装できます。
public static <T> Monoid<List<? extends T>> appendProtectedListsMonoid() {
return new Monoid<List<? extends T>>() {
public List<? extends T> empty() {
return ReadOnlyLists.<T>emptyAppendProtected();
}
public List<? extends T> combine(
List<? extends T> a,
List<? extends T> b
) {
return combineAppendProtected(a, b);
}
};
}
これは、質問で指定された署名を持つメソッドを使用して、いくつかの一般的な設計パターン/代数的構造(モノイド)を実装できることを示しています。確かに、この例はやや工夫が凝らされています。 APIのユーザーをあまり驚かせたくない であるため、実際には使用したくないでしょう。
完全にコンパイル可能な例:
import Java.util.*;
class AppendProtectedLists {
public static <T> List<? extends T> emptyAppendProtected() {
return new LinkedList<T>();
}
public static <T> List<? extends T> combineAppendProtected(
List<? extends T> a,
List<? extends T> b
) {
List<T> result = new LinkedList<T>();
result.addAll(a);
result.addAll(b);
return result;
}
public static <T> List<? extends T> toAppendProtected(List<T> original) {
List<T> result = new LinkedList<T>();
result.addAll(original);
return result;
}
public static <T> List<? extends T> pure(T t) {
List<T> result = new LinkedList<T>();
result.add(t);
return result;
}
public static interface Monoid<X> {
X empty();
X combine(X a, X b);
}
public static <T> Monoid<List<? extends T>> appendProtectedListsMonoid() {
return new Monoid<List<? extends T>>() {
public List<? extends T> empty() {
return AppendProtectedLists.<T>emptyAppendProtected();
}
public List<? extends T> combine(
List<? extends T> a,
List<? extends T> b
) {
return combineAppendProtected(a, b);
}
};
}
public static void main(String[] args) {
Monoid<List<? extends String>> monoid = appendProtectedListsMonoid();
List<? extends String> e = monoid.empty();
// e.add("hi"); // refuses to compile, which is good: write protection!
List<? extends String> a = pure("a");
List<? extends String> b = pure("b");
List<? extends String> c = monoid.combine(e, monoid.combine(a, b));
System.out.println(c); // output: [a, b]
}
}
私は「それは有用なシグネチャーであるか」を「それのユースケースを考えることができるか」を意味すると解釈します。
T
はメソッド内ではなく、呼び出しサイトで決定されるため、メソッドから返すことができるのは、nullまたは空のリストの2つだけです。
このメソッドを呼び出すのとほぼ同じくらい多くのコードでこれらの値の両方を作成できることを考えると、実際にそれを使用する十分な理由はありません。
実際、安全に返すことができる別の値は、すべての要素がnullであるリストです。ただし、型バインドの? extends
があるため、戻り値からリテラルnullを追加または削除するメソッドのみを呼び出すことができるため、これも有用ではありません。つまり、含まれているnullの数を数えるだけです。どちらも役に立ちません。
公式の Generics tutorial は、ワイルドカードの戻り値の型を使用しないことを推奨しています。
これらのガイドラインは、メソッドの戻り値の型には適用されません。戻り値の型としてワイルドカードを使用すると、プログラマーがコードを使用してワイルドカードを処理する必要が生じるため、使用を避けてください。」
しかし、与えられた推論は正確には説得力がありません。
他の回答で与えられた理由のため、それは特に有用ではありません。ただし、次のようなクラスではそれが「有用」であることを考慮してください(おそらく少しアンチパターンですが)。
_public class Repository {
private List<Object> lst = new ArrayList<>();
public <T> void add(T item) {
lst.add(item);
}
@SuppressWarnings("unchecked")
public <T> List<? extends T> f() {
return (List<? extends T>) lst;
}
public static void main(String[] args) {
Repository rep = new Repository();
rep.add(BigInteger.ONE);
rep.add(Double.valueOf(2.0));
List<? extends Number> list = rep.f();
System.out.println(list.get(0).doubleValue() + list.get(1).doubleValue());
}
}
_
次の機能に注意してください。
get(N)
へのすべての呼び出しを必要な型にキャストする必要があります。Collections.unmodifiableList()
を使用して、実行時にリストを強制的に読み取り専用にしますが、ワイルドカードジェネリックパラメーターを使用すると、リストはcompile-timeで強制的に読み取り専用になります。 !