Java泛型何时需要&lt ;?延伸T>而不是< T>并有切换的任何缺点? [英] When do Java generics require <? extends T> instead of <T> and is there any downside of switching?

查看:167
本文介绍了Java泛型何时需要&lt ;?延伸T>而不是< T>并有切换的任何缺点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

 映射< 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>

所以有三个问题:


  1. 为什么现在的版本不能编译?尽管我隐约了解这里的协变问题,但我肯定无法解释它。

  2. 更改 assertThat有什么不利之处方法到匹配器<?扩展T> ?有没有其他的情况下,如果你这样做会破坏?

  3. 是否有任何指向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:

  1. 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.
  2. Is there any downside in changing the assertThat method to Matcher<? extends T>? Are there other cases that would break if you did that?
  3. 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泛型何时需要&lt ;?延伸T&gt;而不是&lt; T&gt;并有切换的任何缺点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆