Yis*_*hai 191 java generics junit
给出以下示例(使用带有Hamcrest匹配器的JUnit):
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
Run Code Online (Sandbox Code Playgroud)
这不能使用JUnit assertThat方法签名编译:
public static <T> void assertThat(T actual, Matcher<T> matcher)
Run Code Online (Sandbox Code Playgroud)
编译器错误消息是:
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>>>)
Run Code Online (Sandbox Code Playgroud)
但是,如果我将assertThat方法签名更改为:
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
Run Code Online (Sandbox Code Playgroud)
然后编译工作.
所以有三个问题:
assertThat方法更改为Matcher<? extends T>?是否有任何缺点?如果你这样做,还有其他案例会破裂吗?assertThat在JUnit 中对方法进行泛化是否有任何意义?该Matcher级似乎并不需要它,因为JUnit的调用matches方法,它不与任何普通类型的,只是看起来像一个企图迫使一个类型安全这并不做任何事情,因为Matcher实际上只会不匹配,无论如何测试都会失败.不涉及不安全的操作(或似乎如此).供参考,以下是JUnit的实现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());
}
}
Run Code Online (Sandbox Code Playgroud)
Sco*_*eld 133
首先 - 我必须引导您访问http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html - 她的工作非常出色.
基本的想法是你使用
<T extends SomeClass>
Run Code Online (Sandbox Code Playgroud)
当实际参数可以是SomeClass它的任何子类型.
在你的例子中,
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
Run Code Online (Sandbox Code Playgroud)
你说它expected可以包含代表任何实现类的Class对象Serializable.您的结果图表示它只能容纳Date类对象.
当您在结果传递,你设定T准确Map的String给Date类对象,这不匹配Map的String任何东西那是Serializable.
要检查的一件事 - 你确定你想要Class<Date>而不是Date吗?的地图String,以Class<Date>不健全的普遍非常有用的(所有它所能容纳的Date.class作为值,而不是实例Date)
至于泛化assertThat,我们的想法是该方法可以确保Matcher传入适合结果类型的方法.
Yis*_*hai 26
感谢所有回答这个问题的人,这真的有助于为我澄清事情.最后,斯科特·斯坦奇菲尔德的答案与我最终理解它的方式最接近,但由于我在第一次写作时并不理解他,所以我试图重申这个问题,以便其他人能够受益.
我将以List的形式重述这个问题,因为它只有一个通用参数,这将使它更容易理解.
参数化类的目的(如示例中的List <Date>或Map <K, V>)是强制向下转换并让编译器保证这是安全的(没有运行时异常).
考虑List的情况.我的问题的实质是为什么一个采用类型T和List的方法不会接受继承链下面的东西的列表而不是T.考虑这个人为的例子:
List<java.util.Date> dateList = new ArrayList<java.util.Date>();
Serializable s = new String();
addGeneric(s, dateList);
....
private <T> void addGeneric(T element, List<T> list) {
list.add(element);
}
Run Code Online (Sandbox Code Playgroud)
这将无法编译,因为list参数是日期列表,而不是字符串列表.如果编译的话,泛型不会非常有用.
同样的事情适用于地图<String, Class<? extends Serializable>>它与地图不同<String, Class<java.util.Date>>.它们不是协变的,所以如果我想从包含日期类的地图中取一个值并将其放入包含可序列化元素的地图中,那很好,但方法签名说:
private <T> void genericAdd(T value, List<T> list)
Run Code Online (Sandbox Code Playgroud)
想要两者兼得:
T x = list.get(0);
Run Code Online (Sandbox Code Playgroud)
和
list.add(value);
Run Code Online (Sandbox Code Playgroud)
在这种情况下,即使junit方法实际上并不关心这些事情,方法签名也需要协方差,而它没有得到,因此它不能编译.
关于第二个问题,
Matcher<? extends T>
Run Code Online (Sandbox Code Playgroud)
当T是一个Object而不是API意图时,会有真正接受任何东西的缺点.目的是静态地确保匹配器匹配实际对象,并且无法从该计算中排除Object.
第三个问题的答案是,在未经检查的功能方面没有任何东西会丢失(如果这个方法没有通用化,JUnit API中就不会有不安全的类型转换),但是他们正试图完成其他事情 - 静态地确保两个参数可能匹配.
编辑(经过进一步的思考和经验):
assertThat方法签名的一个重大问题是尝试将变量T与T的泛型参数等同.这不起作用,因为它们不是协变的.因此,例如,您可能有一个T,List<String>但它然后传递编译器工作的匹配Matcher<ArrayList<T>>.现在如果它不是一个类型参数,事情会没事,因为List和ArrayList是协变的,但是由于Generics,就编译器而言需要ArrayList,它不能容忍List,原因我希望是明确的从上面.
Gre*_*nie 14
归结为:
Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date
Run Code Online (Sandbox Code Playgroud)
你可以看到Class引用c1可以包含一个Long实例(因为在给定时间可能已经存在底层对象List<Long>),但显然不能转换为Date,因为不能保证"unknown"类是Date.它不是典型的,所以编译器不允许它.
但是,如果我们引入一些其他对象,比如说List(在你的例子中这个对象是Matcher),则以下内容成立:
List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile
Run Code Online (Sandbox Code Playgroud)
...但是,如果列表的类型变成了?扩展T而不是T ....
List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile
Run Code Online (Sandbox Code Playgroud)
我认为通过改变Matcher<T> to Matcher<? extends T>,你基本上是在引入类似于分配l1 = l2的场景;
嵌套通配符仍然非常令人困惑,但希望通过查看如何为彼此分配通用引用来理解为什么它有助于理解泛型.由于编译器在进行函数调用时推断出T的类型(你没有明确告诉它是T是),所以它还令人困惑.
你原来的代码不能编译的原因是,<? extends Serializable>它并不意味着,"扩展序列化任何类",而是"延伸序列化的一些未知的,但具体的类."
例如,给定编写的代码,分配new TreeMap<String, Long.class>()>给它是完全有效的expected.如果编译器允许编译代码,assertThat()那么可能会破坏,因为它会期望Date对象而不是Long它在地图中找到的对象.
我理解通配符的一种方法是认为通配符没有指定给定泛型引用可以"拥有"的可能对象的类型,但是它兼容的其他泛型引用的类型(这可能听起来令人困惑) ......)因此,第一个答案在其措辞中非常误导.
换句话说,List<? extends Serializable>意味着您可以将该引用分配给其他类型,其中类型是某些未知类型,或者是Serializable的子类.不要以单个列表的形式来考虑它能够保存Serializable的子类(因为这是错误的语义并导致对泛型的误解).
我知道这是一个老问题,但我想分享一个我认为很好地解释有界通配符的例子。java.util.Collections提供这种方法:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
Run Code Online (Sandbox Code Playgroud)
如果我们有一个 List of T,那么 List 当然可以包含扩展 的类型的实例T。如果列表包含动物,则列表可以同时包含狗和猫(均为动物)。狗有一个属性“woofVolume”,猫有一个属性“meowVolume”。虽然我们可能希望基于这些特定于 的子类的属性进行排序T,但我们怎么能期望这个方法做到这一点呢?Comparator 的一个限制是它只能比较只有一种类型 ( T) 的两个事物。因此,只需要 aComparator<T>将使此方法可用。但是,此方法的创建者认识到,如果某物是T,那么它也是 的超类的实例T。因此,他允许我们使用T或 的任何超类的比较器T,即? super T。