Java 泛型何时需要 <?扩展 T>而不是 <T>切换有什么缺点吗? [英] When do Java generics require <? extends T> instead of <T> and is there any downside of switching?
问题描述
给出以下示例(使用带有 Hamcrest 匹配器的 JUnit):
Given the following example (using JUnit with Hamcrest matchers):
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
这不会与以下 JUnit assertThat
方法签名一起编译:
This does not compile with the JUnit assertThat
method signature of:
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
方法签名更改为:
However, if I change the assertThat
method signature to:
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
然后编译工作.
所以三个问题:
- 为什么当前版本无法编译?虽然我对这里的协方差问题有模糊的理解,但如果必须的话,我当然无法解释.
- 将
assertThat
方法更改为Matcher< 有什么缺点吗?扩展 T>
?如果您这样做,是否还有其他情况会中断? - 在 JUnit 中对
assertThat
方法进行泛化有什么意义吗?Matcher
类似乎不需要它,因为 JUnit 调用了matches 方法,该方法没有使用任何泛型类型化,并且看起来像是试图强制类型安全,但它不做任何事情,因为Matcher
实际上不会匹配,无论如何测试都会失败.不涉及不安全的操作(或看起来如此).
- Why exactly doesn't the current version compile? Although I vaguely understand the covariance issues here, I certainly couldn't explain it if I had to.
- Is there any downside in changing the
assertThat
method toMatcher<? extends T>
? Are there other cases that would break if you did that? - Is there any point to the genericizing of the
assertThat
method in JUnit? TheMatcher
class doesn't seem to require it, since JUnit calls the matches method, which is not typed with any generic, and just looks like an attempt to force a type safety which doesn't do anything, as theMatcher
will just not in fact match, and the test will fail regardless. No unsafe operations involved (or so it seems).
作为参考,这里是assertThat
的JUnit实现:
For reference, here is the JUnit implementation of 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("
Expected: ");
matcher.describeTo(description);
description
.appendText("
got: ")
.appendValue(actual)
.appendText("
");
throw new java.lang.AssertionError(description.toString());
}
}
推荐答案
首先 - 我必须引导您到 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html -- 她做得非常出色.
First - I have to direct you to http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html -- she does an amazing job.
基本思路就是你用
<T extends SomeClass>
当实际参数可以是 SomeClass
或它的任何子类型时.
when the actual parameter can be SomeClass
or any subtype of it.
在你的例子中,
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
您是说 expected
可以包含代表任何实现 Serializable
的类的 Class 对象.您的结果地图说它只能保存 Date
类对象.
You're saying that expected
can contain Class objects that represent any class that implements Serializable
. Your result map says it can only hold Date
class objects.
当你传入结果时,你将 T
设置为 String
的 Map
到 Date
类对象,它不将 String
的 Map
与任何 Serializable
匹配.
When you pass in result, you're setting T
to exactly Map
of String
to Date
class objects, which doesn't match Map
of String
to anything that's Serializable
.
要检查一件事——您确定要Class
而不是Date
?String
到 Class
的映射一般来说听起来不是很有用(它所能保存的只是 Date.class
作为值而不是比 Date
)
One thing to check -- are you sure you want Class<Date>
and not Date
? A map of String
to Class<Date>
doesn't sound terribly useful in general (all it can hold is Date.class
as values rather than instances of Date
)
至于泛化assertThat
,思路是方法可以保证传入一个符合结果类型的Matcher
.
As for genericizing assertThat
, the idea is that the method can ensure that a Matcher
that fits the result type is passed in.
这篇关于Java 泛型何时需要 <?扩展 T>而不是 <T>切换有什么缺点吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!