次の例を考えてみましょう(JUnitをHamcrest matcherと組み合わせて使う):
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<Java.util.Date>> result = null;
assertThat(result, is(expected));
これは、次のJUnitのassertThat
メソッドシグネチャではコンパイルされません。
public static <T> void assertThat(T actual, Matcher<T> matcher)
コンパイラのエラーメッセージは次のとおりです。
Error:Error:line (102)cannot find symbol method
assertThat(Java.util.Map<Java.lang.String,Java.lang.Class<Java.util.Date>>,
org.hamcrest.Matcher<Java.util.Map<Java.lang.String,Java.lang.Class
<? extends Java.io.Serializable>>>)
ただし、assertThat
メソッドのシグネチャを次のように変更したとします。
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
それからコンパイルはうまくいきます。
3つの質問
assertThat
メソッドをMatcher<? extends T>
に変更することにマイナス面がありますか?あなたがそうするならば壊れる他のケースがありますか?assertThat
メソッドを一般化する意味はありますか? JUnitはmatchesメソッドを呼び出すので、Matcher
クラスはそれを必要としていないようです。これは、Matcher
クラスでは不可能なので、何もしない型安全を強制しようとする試みのようです。実際には一致し、テストは関係なく失敗します。危険な操作は含まれていません(またはそう思われます)。参考のために、assertThat
のJUnit実装を示します。
public static <T> void assertThat(T actual, Matcher<T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason);
description.appendText("\nExpected: ");
matcher.describeTo(description);
description
.appendText("\n got: ")
.appendValue(actual)
.appendText("\n");
throw new Java.lang.AssertionError(description.toString());
}
}
最初に - 私はあなたに指示する必要があります http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html - 彼女は素晴らしい仕事をします。
基本的な考え方はあなたが使うということです
<T extends SomeClass>
実パラメータがSomeClass
name__またはそのサブタイプになることができる場合.
あなたの例では、
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<Java.util.Date>> result = null;
assertThat(result, is(expected));
expected
name__には、Serializable
name__を実装するクラスを表すClassオブジェクトを含めることができると言っています。あなたの結果マップはそれがDate
name__クラスのオブジェクトしか保持できないと言っています。
結果を渡すときは、T
name__をMap
name__からString
name__までのクラスオブジェクトの正確なDate
name__に設定します。これは、Map
name__のString
name__とSerializable
name__のいずれにも一致しません。
1つ確認してください - Date
name__ではなくClass<Date>
が必要ですか? String
name__からClass<Date>
へのマップは、一般的にはあまり役に立ちません(保持できるのは、Date
name__のインスタンスではなく、値としてのDate.class
だけです)。
assertThat
name__を一般化することに関しては、結果の型に合ったMatcher
name__が渡されることをメソッドが保証することができるという考えです。
質問に答えてくれたみんなのおかげで、それは私にとって物事を明確にするのに本当に役立ちました。結局、Scott Stanchfieldの答えは私がそれを理解することになった方法に最も近いものになりましたが、彼が最初に書いたときには理解できなかったので、私は問題を言い直すように試みます。
Listには一般的なパラメータが1つしかなく、それによって理解が容易になるので、私はListに関してこの問題をもう一度言います。
パラメータ化されたクラス(例のようにList<Date>
やMap<K, V>
など)の目的は、強制ダウンキャストとし、これが安全であることをコンパイラに保証させることです(ランタイム例外はありません)。
リストの場合を考えます。私の質問の本質は、なぜT型とListを取るメソッドがTよりも継承の連鎖の下にある何かのListを受け入れないのです。この人為的な例を考えてみましょう:
List<Java.util.Date> dateList = new ArrayList<Java.util.Date>();
Serializable s = new String();
addGeneric(s, dateList);
....
private <T> void addGeneric(T element, List<T> list) {
list.add(element);
}
Listパラメータは文字列のリストではなく日付のリストであるため、これはコンパイルされません。これがコンパイルされれば、ジェネリックスはあまり役に立ちません。
同じことがMap<String, Class<? extends Serializable>>
にも当てはまります。これはMap<String, Class<Java.util.Date>>
と同じことではありません。これらは共変ではないので、日付クラスを含むマップから値を取得し、それを直列化可能な要素を含むマップに配置する場合は問題ありませんが、次のようなメソッドシグネチャが必要です。
private <T> void genericAdd(T value, List<T> list)
両方を実行できるようにしたいです。
T x = list.get(0);
そして
list.add(value);
この場合、junitメソッドが実際にこれらのことを気にしなくても、メソッドシグネチャは共分散を必要としますが、それは得られないため、コンパイルされません。
2番目の質問では、
Matcher<? extends T>
TがObjectの場合、実際には何でも受け入れるという欠点があります。これはAPIの意図ではありません。その意図は、マッチャーが実際のオブジェクトと一致することを静的に保証することであり、その計算からオブジェクトを除外する方法はありません。
3番目の質問に対する答えは、未チェックの機能性に関しては何も失われないということです(このメソッドが汎用化されていなければJUnit API内に安全でない型キャストはありません)。 2つのパラメーターが一致する可能性があります。
編集(さらなる熟考と経験の後):
AssertThatメソッドのシグネチャに関する大きな問題の1つは、変数TをTのジェネリックパラメータと同一視しようとすることです。これらは共変ではないため、機能しません。そのため、例えばList<String>
であるTがあっても、コンパイラがMatcher<ArrayList<T>>
に解決するという一致を渡すことができます。 ListとArrayListは共変なので、今はそれが型パラメータでなければ、問題はありませんが、Genericsは、コンパイラに関してはArrayListを必要とするので、リストを許容することはできません。上記から。
それは沸騰する:
Class<? extends Serializable> c1 = null;
Class<Java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date
クラス参照c1にLongインスタンスが含まれている可能性があります(ある時点での基礎となるオブジェクトはList<Long>
であった可能性があるため)。ただし、 "unknown"クラスがDateであるという保証はないため、明らかにDateにキャストできません。安全ではないので、コンパイラはそれを許可しません。
ただし、他のオブジェクト、たとえばListを紹介すると(このオブジェクトはMatcherです)、次のようになります。
List<Class<? extends Serializable>> l1 = null;
List<Class<Java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile
...しかし、リストの型がになったら? Tの代わりにTを拡張します。
List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<Java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile
Matcher<T> to Matcher<? extends T>
を変更することで、基本的にl1 = l2を代入するのと同じようなシナリオを紹介していると思います。
ワイルドカードを入れ子にすることは依然として非常に混乱しますが、一般的な参照を相互に割り当てる方法を検討することで総称を理解するのに役立つ理由については理にかなっています。関数呼び出しをするときにコンパイラがTの型を推論しているので、それはさらに混乱します(あなたはそれがTであると明示的に言っているのではありません)。
元のコードがコンパイルされないのは、<? extends Serializable>
がnotを意味するからです。
たとえば、記述されたとおりのコードであれば、new TreeMap<String, Long.class>()>
をexpected
に代入することは完全に有効です。コンパイラがコードのコンパイルを許可した場合、マップ内で見つかったDate
オブジェクトではなくLong
オブジェクトが想定されるため、assertThat()
はおそらく破損します。
私がワイルドカードを理解する一つの方法は、ワイルドカードは与えられた総称参照が「持つ」ことができる可能なオブジェクトのタイプを指定していないと考えることですが、互換性のある他の総称参照のタイプ...)そのように、最初の答えはその言い回しにおいて非常に誤解を招くものです。
言い換えれば、List<? extends Serializable>
はあなたがその参照を他のリストに割り当てることができることを意味します。 A SINGLE LISTがSerializableのサブクラスを保持できるという点でそれを考えてはいけません(それは誤った意味論であり、Genericsの誤解につながります)。
私はこれが昔からの質問であることを知っていますが、私は境界ワイルドカードをかなりよく説明していると思う例を共有したいと思います。 Java.util.Collections
はこのメソッドを提供します:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
T
のListがある場合、当然、ListはT
を拡張する型のインスタンスを含むことができます。リストに動物が含まれる場合、リストには犬と猫の両方(両方の動物)を含めることができます。犬には「woofVolume」というプロパティがあり、猫には「meowVolume」というプロパティがあります。 T
のサブクラスに特有のこれらのプロパティに基づいてソートしたいと思うかもしれませんが、どのようにしてこのメソッドがそれを行うことができるのでしょうか。 Comparatorの制限は、1つの型(T
)のうち2つしか比較できないことです。そのため、単にComparator<T>
を要求するだけでこのメソッドは使用可能になります。しかし、このメソッドの作成者は、何かがT
であれば、それもT
のスーパークラスのインスタンスであることを認識していました。したがって、彼はT
のコンパレーターまたはT
のスーパークラス、つまり? super T
の使用を許可します。
あなたが使用する場合はどうなります
Map<String, ? extends Class<? extends Serializable>> expected = null;