Java泛型何时需要< ;?延伸T>而不是< T>并有切换的任何缺点? [英] When do Java generics require <? extends T> instead of <T> and is there any downside of switching?
问题描述
映射< String,Class< ;?????????????????????????????????????????????????????????????扩展Serializable>>预期=空;
Map< String,Class< java.util.Date>> result = null;
assertThat(result,is(expected));
这不会使用JUnit assertThat
方法签名:
public static< T> void assertThat(T actual,Matcher< T> matcher)
编译器错误信息是:
错误:错误:行(102)找不到符号方法
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
<?如果我改变 assertThat
方法签名为:
public static< T> void assertThat(T result,Matcher< ;? extends T> matcher)
然后编译工作。 p>
所以有三个问题:
- 为什么现在的版本不能编译?尽管我隐约了解这里的协变问题,但我肯定无法解释它。
- 更改
assertThat有什么不利之处
方法到匹配器<?扩展T>
?有没有其他的情况下,如果你这样做会破坏?
- 是否有任何指向JUnit中的
assertThat
方法的泛化? Matcher
类似乎并不需要它,因为JUnit调用了不与任何泛型类型匹配的matches方法,而且看起来像试图强制类型安全它不会做任何事情,因为 Matcher
实际上不会匹配,并且无论如何测试都会失败。不涉及不安全的操作(或如此)。
作为参考,这里是的JUnit实现assertThat $ b
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 = new StringDescription();
description.appendText(reason);
description.appendText(\\\
Expected:);
matcher.describeTo(description);
描述
.appendText(\\\
got:)
.appendValue(actual)
.appendText(\\\
);
抛出新的java.lang.AssertionError(description.toString());
}
}
解决方案 - 我必须引导您访问 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html - 她做了一件了不起的工作。
基本想法是,您使用
< T extends SomeClass>
当实际参数可以是 SomeClass
或
在你的例子中,
Map< / p>字符串,类<?扩展Serializable>>预期=空;
Map< String,Class< java.util.Date>> result = null;
assertThat(result,is(expected));
您的意思是 expected 可以包含表示实现 Serializable
的任何类的Class对象。你的结果图表示它只能容纳 Date
类对象。
当你传入结果时, T
到 Map 字符串
到 Date
类对象,它与 Map 的 String
不匹配可序列化
。
要检查的一件事 - 您确定要使用 Class< Date> ;
而不是日期
?一般来说, String
到 Class 的映射听起来并不是非常有用(它所能容纳的全部是 Date.class
作为值而不是 Date
的实例)
对于泛化 assertThat
,想法是该方法可以确保符合结果类型的 Matcher
被传递in。
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));
This does not compile with the JUnit assertThat
method signature of:
public static <T> void assertThat(T actual, Matcher<T> matcher)
The compiler error message is:
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>>>)
However, if I change the assertThat
method signature to:
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
Then the compilation works.
So three questions:
- 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 to Matcher<? 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? The Matcher
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 the Matcher
will just not in fact match, and the test will fail regardless. No unsafe operations involved (or so it seems).
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("\nExpected: ");
matcher.describeTo(description);
description
.appendText("\n got: ")
.appendValue(actual)
.appendText("\n");
throw new java.lang.AssertionError(description.toString());
}
}
解决方案 First - I have to direct you to http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html -- she does an amazing job.
The basic idea is that you use
<T extends SomeClass>
when the actual parameter can be SomeClass
or any subtype of it.
In your example,
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
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.
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
.
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
)
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屋!