我正在使用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作为参数,以匹配Container内的元素。
答案 0 :(得分: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的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)
Here是您发布的原始代码的完全修改版本,可以为我成功编译。
答案 1 :(得分:1)
matt正好在<? 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
,否则无法知道he T
是什么。
语言规范定义了上述赋值语句中的推理规则,因为开发人员的意图绝对清楚T应该是String
更多推理规则的倡导者必须提供他们想要的确切规则集,证明这些规则是安全可靠的,并且对于凡人来说是可理解的。
答案 2 :(得分:1)
我能够创建一些解决方法来实现所需的语法。
选项1
一种解决方法是为assertThat
方法创建替换,以便将Container<T>
作为参数。当方法在不同的类中时,替换断言方法甚至应该能够具有相同的名称。
这需要在? 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);
}
}