3

Hamcrest 1.2ライブラリを使用していくつかのマッチャーを作成していますが、Java ワイルドカードに苦労しています。次のコードをコンパイルしようとすると

public class GenericsTest {

    public void doesNotCompile() {
        Container<String> container = new Container<String>();

        // this is the desired assertion syntax
        assertThat(container, hasSomethingWhich(is("foo")));
    }

    // these two are a custom made class and matcher; they can be changed

    public static class Container<T> {
        public boolean hasSomethingMatching(Matcher<T> matcher) {
            T something = null; // here is some application logic
            return matcher.matches(something);
        }
    }

    public static <T> Matcher<Container<T>> hasSomethingWhich(final Matcher<T> matcher) {
        return new TypeSafeMatcher<Container<T>>() {
            @Override
            protected boolean matchesSafely(Container<T> container) {
                return container.hasSomethingMatching(matcher);
            }
        };
    }

    // the following signatures are from the Hamcrest 1.2 library; they cannot be changed

    public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
    }

    public static <T> Matcher<? super T> is(T value) {
        return null;
    }

    public interface Matcher<T> {
        boolean matches(Object item);
    }

    public static abstract class TypeSafeMatcher<T> implements Matcher<T> {
        @SuppressWarnings({"unchecked"})
        @Override
        public final boolean matches(Object item) {
            return matchesSafely((T) item);
        }

        protected abstract boolean matchesSafely(T item);
    }
}

コンパイルエラーが発生します

$ javac GenericsTest.java
GenericsTest.java:7: <T>assertThat(T,GenericsTest.Matcher<? super T>) in GenericsTest cannot be applied to (GenericsTest
.Container<java.lang.String>,GenericsTest.Matcher<GenericsTest.Container<capture#928 of ? super java.lang.String>>)
        assertThat(container, hasSomethingWhich(is("foo")));
        ^
1 error

コンパイルできるようにコードを変更するにはどうすればよいですか? ? superContainer クラスと hasSomethingWhich メソッドのシグネチャのとのさまざまな組み合わせを試しまし? extendsたが、コンパイルできませんでした (明示的なメソッド型パラメーターを使用せずに、醜いコードを生成します: GenericsTest.<String>hasSomethingWhich)。

また、簡潔で読みやすいアサーション構文を作成するための代替アプローチも歓迎します。構文に関係なく、Container と、Container 内の要素を照合するための Matcher をパラメーターとして受け入れる必要があります。

4

3 に答える 3

3

マッチャーは、署名が。であるis(T)マッチャーを返しますMatcher<? super T>

したがって、ラインを分解すると、assertThat(container, hasSomethingWhich(is("foo")))実際にあるものは次のようになります。

Matcher<? super String> matcher = is("foo");
assertThat(container, hasSomethingWhich(matcher));

hasSomethingWhichメソッドのシグニチャにはパラメータ.が必要なため、2行目にコンパイルエラーがありますMatcher<T>。ハムクレストの返品タイプと一致させるis(T)には、代わりに署名を次のようにする必要があります。

public static <T> Matcher<Container<T>> hasSomethingWhich(final Matcher<? super T> matcher)

(違いは、パラメーターをからMatcher<T>に変更することMatcher<? super T>です。

hasSomethingWhich()これにより、の署名を変更して、同様のものも受け入れるMatcher<? super T>ように強制されます。

public boolean hasSomethingMatching(Matcher<? super T> matcher)

これがあなたが投稿した元のコードの完全に修正されたバージョンで、私のために正常にコンパイルされます。

于 2010-09-30T14:29:41.507 に答える
1

目的の構文を実現するために、いくつかの回避策を作成することができました。

オプション1

assertThat回避策の1つは、メソッドの置換を作成してContainer<T>、パラメーターとしてを使用することです。メソッドが異なるクラスにある場合、置換assertメソッドは同じ名前を持つことができるはずです。

? superこれには、たとえばのreturn型に奇妙な追加が必要でhasSomethingWhichあり、の型パラメータをhasSomethingMatching緩和する必要がありました。そのため、コードが理解しにくくなります。

public class GenericsTest {

    public void doesNotCompile() {
        Container<String> container = new Container<String>();

        // this is the desired assertion syntax
        assertThat2(container, hasSomethingWhich(is("foo")));
    }

    public static <T> void assertThat2(Container<T> events, Matcher<? super Container<T>> matcher) {
        assertThat(events, matcher);
    }

    // these two are a custom made class and matcher; they can be changed

    public static class Container<T> {
        public boolean hasSomethingMatching(Matcher<?> matcher) {
            T something = null; // here is some application logic
            return matcher.matches(something);
        }
    }

    public static <T> Matcher<Container<? super T>> hasSomethingWhich(final Matcher<? super T> matcher) {
        return new TypeSafeMatcher<Container<? super T>>() {
            @Override
            protected boolean matchesSafely(Container<? super T> container) {
                return container.hasSomethingMatching(matcher);
            }
        };
    }

    // the following signatures are from the Hamcrest 1.2 library; they cannot be changed

    public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
    }

    public static <T> Matcher<? super T> is(T value) {
        return null;
    }

    public interface Matcher<T> {
        boolean matches(Object item);
    }

    public static abstract class TypeSafeMatcher<T> implements Matcher<T> {
        @SuppressWarnings({"unchecked"})
        @Override
        public final boolean matches(Object item) {
            return matchesSafely((T) item);
        }

        protected abstract boolean matchesSafely(T item);
    }
}

オプション2

もう1つの解決策は、はるかに簡単ですが、型パラメーターをあきらめて、を使用すること<?>です。とにかく、テストは実行時に型の不一致があるかどうかを検出するため、コンパイル時の型の安全性はほとんど役に立ちません。

public class GenericsTest {

    public void doesNotCompile() {
        Container<String> container = new Container<String>();

        // this is the desired assertion syntax
        assertThat(container, hasSomethingWhich(is("foo")));
    }

    // these two are a custom made class and matcher; they can be changed

    public static class Container<T> {
        public boolean hasSomethingMatching(Matcher<?> matcher) {
            T something = null; // here is some application logic
            return matcher.matches(something);
        }
    }

    public static Matcher<Container<?>> hasSomethingWhich(final Matcher<?> matcher) {
        return new TypeSafeMatcher<Container<?>>() {
            @Override
            protected boolean matchesSafely(Container<?> container) {
                return container.hasSomethingMatching(matcher);
            }
        };
    }

    // the following signatures are from the Hamcrest 1.2 library; they cannot be changed

    public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
    }

    public static <T> Matcher<? super T> is(T value) {
        return null;
    }

    public interface Matcher<T> {
        boolean matches(Object item);
    }

    public static abstract class TypeSafeMatcher<T> implements Matcher<T> {
        @SuppressWarnings({"unchecked"})
        @Override
        public final boolean matches(Object item) {
            return matchesSafely((T) item);
        }

        protected abstract boolean matchesSafely(T item);
    }
}
于 2010-10-01T13:35:27.100 に答える
1

マットはちょうどいい<? super T>ですhasSomethingMatching()/hasSomethingWhich()

それを機能させるには:

    Matcher<Container<String>>  tmp = hasSomethingWhich(is("foo"));
    assertThat(container, tmp);

tmp変数が必要です。javac は、任意の式ではなく、その代入で T==String を推論するだけです。(または、メソッドを呼び出すときに T を String として明示的に指定できます)。

Eclipse が推論規則を緩和する場合、それは言語仕様に反します。この例で、それが不適切である理由を見てみましょう:

public static <T> Matcher<Container<T>> 
hasSomethingWhich(final Matcher<? super T> matcher)

この方法は本質的に危険です。を指定するMatch<Object>と、Matcher<Container<Foo>>どこFooでも何でもかまいません。T呼び出し元が明示的に提供するか、コンパイラがコンテキストTから推測しなければならない場合を除き、一体何が何であるかを知る方法はありません。T

言語仕様は、上記の割り当てステートメントで推論規則を定義します。開発者の意図は、 T が正確であるべきであることは完全に明確であるためです。String

より多くの推論ルールを支持する人は、彼らが望む正確なルールのセットを提供し、そのルールが安全で堅牢であり、人間が理解できることを証明しなければなりません。

于 2010-09-30T16:52:59.433 に答える