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

如何修改代码以便编译?我在 Container 类和 hasSomethingWhich 方法的签名中尝试了不同的组合? super? extends但无法使其编译(不使用显式方法类型参数,但这会产生丑陋的代码:)GenericsTest.<String>hasSomethingWhich

也欢迎创建简洁易读的断言语法的替代方法。不管是什么语法,它都应该接受一个 Container 和一个 Matcher 作为参数来匹配 Container 内的元素。

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方法的签名需要一个Matcher<T>. 要匹配 hamcrest's 的返回类型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

马特是对<? 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)

这种方法本质上是危险的。给定 a Match<Object>,它可以返回 a Matcher<Container<Foo>>whereFoo可以是任何东西。T除非调用者明确提供,或者编译器必须从上下文T中推断,否则无法知道到底是什么。T

语言规范在上面的赋值语句中定义了推理规则,因为开发者的意图是绝对清楚的,T 应该是String

更多推理规则的拥护者必须提供他们想要的确切规则集,证明这些规则是安全和稳健的,并且对于凡人来说是可以理解的。

于 2010-09-30T16:52:59.433 回答
1

我能够创建几个解决方法来实现所需的语法。

选项1

一种解决方法是为该assertThat方法创建一个替换,以便它接受一个Container<T>as 参数。当方法位于不同的类中时,替换的断言方法甚至应该能够具有相同的名称。

这需要奇怪的添加,? super例如在返回类型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

另一种更简单的解决方案是放弃类型参数而只使用<?>. 无论如何,测试将在运行时发现是否存在类型不匹配,因此编译时类型安全几乎没有用处。

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 回答