【问题标题】:Java 8 generic collections with optionals带有选项的 Java 8 泛型集合
【发布时间】:2015-04-24 22:41:34
【问题描述】:

我正在尝试解决一个看起来相对简单的问题。似乎没有一种直观的方法可以做到这一点,或者我在这里遗漏了一些东西。

考虑这种方法来查找主图像,如果不存在,则返回第一张图像-

public Image findMainImage(Collection<? extends Image> images) {
    if (images == null || images.isEmpty()) return null
    return images.stream()
                 .filter(Image::isMain)
                 .findFirst()
                 .orElse(images.iterator().next())
}

我收到一个错误 - orElse(capture&lt;? extends Image&gt;) in Optional cannot be applied

这方面的任何方向都会很棒。

【问题讨论】:

  • 如果它只是 Collection&lt;Image&gt;,这可能会起作用,但泛型的工作方式,Optional&lt;T&gt;.orElse 只能接受 T
  • 能不能把方法签名改成public&lt;T extends Image&gt; T findMainImage(Collection&lt;T&gt; images)
  • @misha 我通过更改签名了解了它的工作原理,我更感兴趣的是了解这背后的原因。
  • 如果您使用带有通配符子类型 (? extends ...) 的集合的返回值,而不将其绑定到确定类型(例如,通过将其分配给类型为 Image 的变量,您'会有一个? extends Image 类型,而orElse 方法由于类型推断需要Image。Java 泛型是一种痛苦,尤其是在涉及通配符时。
  • @sanz 推理是知道参数化流的? extends Imageimages.iterator().next()产生的? extends Image相同,但编译器不知道.通过更改签名并为集合参数命名,您可以向编译器证明它在两个地方都是相同的类型。

标签: java generics java-8 optional bounded-wildcard


【解决方案1】:

修复它的一种方法是使用类型参数:

public <I extends Image> I findMainImage(Collection<I> images) {
    if (images == null || images.isEmpty()) return null;
    return images.stream()
                 .filter(Image::isMain)
                 .findFirst()
                 .orElse(images.iterator().next());
}

因为(对于编译器)Optional 肯定与images 具有相同的类型参数。

如果我们愿意,我们可以将其用作capturing helper

public Image findMainImage(Collection<? extends Image> images) {
    return findMainImageHelper( images );
}

private <I extends Image> I findMainImageHelper(Collection<I> images) {
    // ...
}

就个人而言,我只会使用通用版本,因为这样你就可以这样做:

List<ImageSub> list = ...;
ImageSub main = findMainImage( list );

基本上......为什么它最初无法编译的原因是为了防止你做这样的事情:

public Image findMainImage(
    Collection<? extends Image> images1,
    Collection<? extends Image> images2
) {
    return images1.stream()
                  .filter(Image::isMain)
                  .findFirst()
                  .orElse(images2.iterator().next());
}

在原始示例中,编译器不需要确定StreamIterator 来自同一个对象这一事实。引用同一对象的两个单独的表达式被捕获为两个单独的类型。

【讨论】:

  • 或者,您可以使用Collections.unmodifiableCollection(images).stream(). …
【解决方案2】:

假设您有一个List&lt;? extends Number&gt;。您无法将任何号码添加到此列表中,因为它可能是List&lt;Integer&gt;,而您尝试添加的号码可能是Float

出于同样的原因,Optional&lt;T&gt; 的方法orElse(T t) 需要T。由于所讨论的OptionalOptional&lt;? extends Image&gt;,因此编译器无法确定images.iterator().next() 的类型是否正确。

我通过输入 .map(t -&gt; (Image) t) 来编译它:

return images.stream()
             .filter(Image::isMain)
             .findFirst()
             .map(t -> (Image) t)
             .orElse(images.iterator().next());

事实上,出于某种我无法理解的原因,即使没有演员表也能正常工作。只是使用

.map(t -> t)

似乎可以做到这一点。

【讨论】:

  • 我假设您在findFirst 之前确实有.mapt 的类型是集合的上限Image,所以无论你将t 转换为Image 还是不, it's an Image, and the Stream` 返回现在都是Stream&lt;Image&gt;,而不是Stream&lt;? extends Image&gt;Optional 现在是 Optional&lt;Image&gt; 而不是 Optional&lt;? extends Image&gt;,这允许您将 Image 传递给 orElse
  • @rgettman map 可以在任一位置。 Optional 也有 map 方法,所以我将 Optional&lt;? extends Image&gt; 映射到 Optional&lt;Image&gt;
  • 我什至没有意识到Optional 也有一个map 方法。有句话叫“你每天都学到新东西”,而我只是学到了新东西。
  • @rgettman 不管怎样,奇怪的是你不需要演员,不是吗?编译器如何知道所需的类型是Image
  • StreamMap 的方法都采用 Function&lt;? super T, ? extends R&gt;。编译器可能会根据orElse 方法的参数类型推断Function 的返回类型,但我无法确定。
猜你喜欢
  • 1970-01-01
  • 2012-02-14
  • 2017-04-28
  • 2013-11-19
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 2017-12-24
  • 1970-01-01
相关资源
最近更新 更多