Java泛型和通配符:如何使这段代码编译?
我正在使用 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
如何修改代码以使其能够编译?我尝试过 的不同组合?超级和<代码>? extends
在 Container 类和 hasSomethingWhich 方法的签名中,但无法使其编译(不使用显式方法类型参数,但这会产生丑陋的代码:GenericsTest.
)。
还欢迎用于创建简洁且可读的断言语法的替代方法。无论语法如何,它都应该接受 Container 和 Matcher 作为参数,用于匹配 Container 内的元素。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
is(T)
匹配器返回一个签名为Matcher
。因此,如果您解构行
assertThat(container, hasSomethingWhich(is("foo")))
您真正拥有的是:第二行有一个编译错误,因为您的
hasSomethingWhich< 的签名/code> 方法需要
Matcher
参数。为了匹配 hamcrest 的is(T)
返回类型,您的签名应该是:(区别在于将参数从
Matcher
更改为Matcher< 所示
然后,这将迫使您更改
hasSomethingWhich()
的签名以接受Matcher
,如下: a href="http://gist.github.com/604664" rel="nofollow">Here 是您发布的原始代码的完全修改版本,它对我来说可以成功编译。
The
is(T)
matcher returns a Matcher whose signature isMatcher<? super T>
.So if you deconstruct the line
assertThat(container, hasSomethingWhich(is("foo")))
what you really have is:The second line has a compilation error because the signature of your
hasSomethingWhich
method requires a parameter ofMatcher<T>
. To match the return type of hamcrest'sis(T)
, your signature should instead be:(the difference is changing the parameter from
Matcher<T>
toMatcher<? super T>
.This will then force you to change the signature of
hasSomethingWhich()
to also accept aMatcher<? super T>
like so:Here is the fully modified version of the original code you posted which compiles successfully for me.
马特关于
hasSomethingMatching()/hasSomethingWhich()
中的 super T>使其工作:
tmp
变量是必需的,javac 只会推断 T==String赋值,而不是任意表达式。 (或者您可以在调用该方法时将 T 显式指定为 String)。如果 eclipse 放松了推理规则,那就违反了语言规范。让我们在这个例子中看看为什么它不合适:
这种方法本质上是危险的。给定
Match
语言规范定义了上述赋值语句中的推理规则,因为开发人员的意图绝对明确,T 应该恰好是
String
更多推理规则的倡导者必须提供他们想要的精确规则集,证明这些规则安全、稳健,并且对于凡人来说都是可以理解的。
matt is right about
<? super T>
inhasSomethingMatching()/hasSomethingWhich()
to make it work:
the
tmp
variable is necessary, javac will only infer T==String in that assignment, not in arbitrary expressions. (or you can explicitly specify T as String when invoking the method).If eclipse relaxes the inference rules, that is against the language spec. Let' see in this example why it is inappropriate:
This method is inherently dangerous. given a
Match<Object>
, it can return aMatcher<Container<Foo>>
whereFoo
can be anything. There is no way to know what the heckT
is, unless the caller suppliesT
explicitly, or the compiler has to inferT
from the context.The language spec defines the inference rule in the above assignment statement, because the developer intention is absolutely clear that T should be exactly
String
Advocates of more inference rules must supply the exact set of rules they want, prove that the rules are safe and robust, and comprehensible to mortals.
我能够创建一些解决方法来实现所需的语法。
选项 1
一种解决方法是创建
assertThat
方法的替代方法,以便它采用Container
作为参数。当方法位于不同的类中时,替换断言方法甚至应该能够具有相同的名称。这需要奇怪的添加
?例如,
hasSomethingWhich
的返回类型和hasSomethingMatching
的类型参数必须放宽。所以代码变得很难理解。选项 2
另一种解决方案要简单得多,是放弃类型参数,只使用
。无论如何,测试都会在运行时发现是否存在类型不匹配,因此编译时类型安全几乎没有什么用处。
I was able to create a couple of workarounds to achieve the desired syntax.
Option 1
One workaround is to create a replacement for the
assertThat
method, so that it takes aContainer<T>
as parameter. The replacement assert method should even be able to have the same name, when the methods are in different classes.This requires weird additions of
? super
for example in the return type ofhasSomethingWhich
and the type parameter ofhasSomethingMatching
had to be relaxed. So the code becomes hard to understand.Option 2
The other solution, which is much simpler, is to give up on the type parameters and just use
<?>
. The tests will anyways find out at runtime if there is a type mismatch, so compile time type safety is of little use.