1. ホーム

[解決済み】Javaのジェネリックで<T>の代わりに<? extends T>が必要になるのはどんなときか、また切り替えることによるデメリットはあるか?

2022-04-18 03:38:44

質問

次の例を考えてみましょう(JUnitとHamcrest matchersを使用)。

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つの質問。

  1. なぜ今のバージョンはコンパイルできないのですか?共分散の問題はなんとなくわかっているのですが、説明しようにも説明しきれません。
  2. を変更することで何か不都合はありますか? assertThat メソッドから Matcher<? extends T> ? そうすると壊れるケースが他にもあるのでしょうか?
  3. を汎用化することに意味はあるのでしょうか? assertThat メソッドを JUnit で使用できますか?その Matcher クラスはそれを必要としないようです。JUnit はジェネリックで型付けされていない matches メソッドを呼び出すので、何もしない型安全性を強制しようとしているようにしか見えません。 Matcher は実際にはマッチしないだけで、テストは関係なく失敗します。安全でない操作は関係ありません(というか、そう見えます)。

参考までに、以下はJUnitの実装です。 assertThat :

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 またはそのサブタイプ。

あなたの例では

Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));

あなたが言っているのは expected を実装している任意のクラスを表すClassオブジェクトを含むことができます。 Serializable . あなたの結果マップでは Date クラス・オブジェクトを作成します。

resultを渡すときは T を正確に MapString から Date クラスオブジェクトにはマッチしませんが MapString であるものには Serializable .

一つ確認しておきたいのは、本当に Class<Date> でなく Date ? のマップは StringClass<Date> は、一般的にはあまり便利とは言えないようです(保持できるのは Date.class のインスタンスではなく、値として Date )

一般化については assertThat を確実に実行できるようにすることです。 Matcher というように、結果の型に合ったものが渡されます。