【问题标题】:How can I mock the return from a generic wildcard如何模拟通用通配符的返回
【发布时间】:2016-12-07 10:48:01
【问题描述】:

我试图了解 java 8 中的泛型和通配符。但我不明白为什么不能模拟这个存储库方法。 代码很简单,应该很容易复现。

我在“when”的“thenReturn”部分遇到了这个编译错误

The method thenReturn(Stream<capture#1-of ? extends Something>) in the type
OngoingStubbing<Stream<capture#1-of ? extends Something>> is not applicable
for the arguments (Stream<capture#3-of ? extends Something>)

测试:

@Test
public void ItShourReturnStreamFromRepository() {
    List<Something> list = new ArrayList<Something>();
    list.add(new Something());
    Stream<? extends Something> stream = list.stream();
    when(someRepository.getStream()).thenReturn(stream);     
}

班级:

public class Something {}

存储库:

public interface SomeRepository{
    Stream<? extends Something> getStream();
}

有人可以帮忙吗? 谢谢!

【问题讨论】:

  • 遵循the official tutorial的建议:应避免使用通配符作为返回类型……
  • 在界面中添加一些类型怎么样? interface SomeRepository&lt;T extends Something&gt; { Stream&lt;T&gt; getStream(); }

标签: java java-8 mockito wildcard java-stream


【解决方案1】:

这是通配符类型的一般问题。用一个简化的、独立于 Mockito 的示例来演示它:

Enum<?> e = Thread.State.BLOCKED;

// produces "incompatible types: Enum<CAP#1> cannot be converted to Enum<CAP#2>"
Arrays.asList(e).set(0, e);

// works since Java 8
List<Enum<?>> list=Arrays.asList(e);
list.add(e);

// works even in older versions
Arrays.<Enum<?>>asList(e).set(0, e);

这为您指出了应该与 Mockito API 一起使用的可能解决方案。但是,要设计像 SomeRepository 这样的 API,您应该遵循一般的 “Guidelines for Wildcard Use”

应避免使用通配符作为返回类型,因为它会迫使程序员使用代码来处理通配符。

Stream 元素类型中的? extends 会产生复杂性,但没有任何好处。您始终可以创建Stream&lt;Something&gt;,即使来自具有更特定类型的源,例如

SubTypeOfSomething subTyped = …

// from specific values
Stream<Something> s1 = Stream.of(subTyped);

// from existing collection
Set<SubTypeOfSomething> set = Collections.singleton(subTyped);
// note: this creates the stream directly from the source set
Stream<Something> s2 = Collections.<Something>unmodifiableSet(set).stream();

// if you really unavoidably have a more specifically typed Stream
Stream<SubTypeOfSomething> s3 = set.stream();
Stream<Something> s4 = s3.map(Function.identity());

Stream&lt;? extends Something&gt; 不提供 Stream&lt;Something&gt; 不提供的任何东西。

如果是输入界面,那就是另一回事了,比如关注RC’s suggestion

interface SomeRepository<T extends Something> {
    Stream<T> getStream();
}

然后,您不再有通配符,更具体的类型流可能会带来好处。

【讨论】:

  • “Stream extends Something> 不提供 Stream 也不提供的任何东西。” 这并不完全正确。前者作为返回类型,该方法可以返回Something子类的Stream,而后者则不能。该方法的外部用户可能不关心它得到了什么特定的子类,因此强制使用完全类型化的接口可能并不总是正确的选择。有时使用通配符的返回类型是最合适的。
  • @quiram 使用Stream&lt;T&gt; 作为返回类型的示例确实 已经允许流具有Something 的任意子类型作为元素类型。如果还有代码需要特定的T,那仍然有好处。拥有Stream&lt;? extends Something&gt; 根本没有任何好处。无论原始元素类型如何,生成该流的任何代码都可以轻松调整为返回Stream&lt;Something&gt;。只需调整确定最终流类型的最后一个操作,例如mapflatMap。喜欢stream.&lt;Something&gt;map(x -&gt; expression-producing-a-subtype-of-Something)
  • 我同意很多代码可以按照您建议的方式轻松调整,但不是所有代码。我自己有一个特殊的例子,这就是为什么我一开始就偶然发现了这个问题。您可能有不同的类您无法控制,其中一个有一个返回Stream&lt;SomethingA&gt; 的方法,另一个返回Stream&lt;SomethingB&gt;SomethingASomethingB 都扩展了 Something。我想要一个可以调用这两个中的 either 并返回结果的包装方法。 Stream&lt;Something&gt; 的返回类型不会编译,您需要 Stream&lt;? extends Something&gt;
  • @quiram 或者你附加一个简单的.&lt;Something&gt;map(Function.identity())
  • true,这将适用于流,但不适用于所有泛型。在我的情况下,我并没有真正的流,所以我不能映射(我谈到流是为了更容易解释,但它实际上是一个自定义类)。我想棘手的部分是您提出的解决方案可以与 OP 的示例代码一起使用(我同意这是一个更清洁的解决方案),但不适用于标题中更一般的问题。
【解决方案2】:

抛开带有通配符的返回类型是好还是坏的问题不谈,这个问题可以使用 Mockito 来解决,方法是通过 doReturn().when() 而不是 when().thenReturn() 进行模拟。 doReturn() 模式不是类型安全的,因此建议仅在没有更好的选项时使用它(例如在这种情况下)。这样,您的测试可以写成:

@Test
public void ItShourReturnStreamFromRepository() {
    List<Something> list = new ArrayList<Something>();
    list.add(new Something());
    Stream<? extends Something> stream = list.stream();
    doReturn(stream).when(someRepository).getStream();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多