在Java 8中,是否可以将Stream转换为其他类型?

213

在Java 8中,是否可能将流转换为另一种形式?比如说,如果我有一个对象列表,我可以像这样过滤掉所有额外的对象:

Stream.of(objects).filter(c -> c instanceof Client)

但在此之后,如果我想对客户端执行某些操作,我需要对每个客户端进行转换:

Stream.of(objects).filter(c -> c instanceof Client)
    .map(c -> ((Client) c).getID()).forEach(System.out::println);

这看起来有点丑陋。是否可能将整个流转换为不同类型?例如将Stream<Object>转换为Stream<Client>

请忽略这样做可能意味着糟糕设计的事实。我们在我的计算机科学课上做这样的事情,所以我正在研究Java 8的新功能,想知道是否可能。


3
从Java运行时的角度来看,这两种Stream类型已经是相同的了,因此不需要进行强制转换。关键是要在编译器中悄悄地通过它。(假设这么做有意义的话。) - Hot Licks
5个回答

381

我认为没有现成的方法来做到这一点。可能更加简洁的解决方案是:

Stream.of(objects)
    .filter(c -> c instanceof Client)
    .map(c -> (Client) c)
    .map(Client::getID)
    .forEach(System.out::println);

或者,正如评论中建议的那样,您可以使用 cast 方法-尽管前者可能更易于阅读:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);

这基本上就是我在寻找的。我想我忽视了将它转换为 map 中的客户端,这将返回一个 Stream<Client>。谢谢! - Phiction
+1 有趣的新方法,尽管它们可能会冒着成为新一代类型(水平而不是垂直)的意大利面代码的风险。 - robermann
51
您可以使用以下代码简化instanceOf筛选器: Stream.of(objects).filter(Client.class::isInstance).[...] (注意:中括号内的部分需要您完成翻译) - Nicolas Labrot
这对于未经过类型定义的流不起作用。例如 entityManager.createNativeQuery("SQL", Tuple.class).getResultStream().map(Tuple.class::cast).filter(t -> t.get(0) != null)。它无法编译,因为它找不到 .get()。即使您在 lambda 中扩展参数类型,它仍然无法工作。 - T3rm1
1
@T3rm1 当您使用原始类型时,许多事情会变得更加困难... - assylias
显示剩余2条评论

18

参考ggovan的答案,我按照如下方式执行:

/**
 * Provides various high-order functions.
 */
public final class F {
    /**
     * When the returned {@code Function} is passed as an argument to
     * {@link Stream#flatMap}, the result is a stream of instances of
     * {@code cls}.
     */
    public static <E> Function<Object, Stream<E>> instancesOf(Class<E> cls) {
        return o -> cls.isInstance(o)
                ? Stream.of(cls.cast(o))
                : Stream.empty();
    }
}

使用这个帮助函数:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);

13

虽然来晚了,但我认为这是一个有用的答案。

flatMap 是最短的方法来实现它。

Stream.of(objects).flatMap(o->(o instanceof Client)?Stream.of((Client)o):Stream.empty())

如果o是一个Client,则创建一个只包含一个元素的Stream,否则使用空Stream。然后将这些Stream展平成一个Stream<Client>


我尝试实现这个功能,但是收到一个警告,说我的类“使用了未经检查或不安全的操作” - 这是正常现象吗? - aweibell
不幸的是,是的。如果您使用if/else而不是?:运算符,则不会出现警告。请放心,您可以安全地忽略此警告。 - ggovan
5
实际上,这比 Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o) 还要长,甚至比 Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast) 长。 - Didier L

8
这看起来有点丑陋。是否有可能将整个流转换为不同类型?比如将Stream<Object> 转换为 Stream<Client>
不,这是不可能的。这不是Java 8中的新特性,而是泛型特性。一个List<Object> 不是 List<String> 的超类,所以你不能直接将一个List<Object> 强制转换为 List<String>
这里也是同样的问题。你不能将Stream<Object> 强制转换为 Stream<Client>。当然,你可以间接地强制转换,像这样:
Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;

但这并不安全,而且在运行时可能会失败。其根本原因是,在Java中使用擦除实现泛型。因此,在运行时没有关于Stream类型的信息可用。所有东西都只是Stream

顺便问一下,你的方法有什么问题吗?在我看来很好。


2
C#中,泛型是通过具体化实现的,而在Java中则是通过擦除实现的。两者的底层实现方式不同,因此不能指望它们在两种语言中的工作方式相同。 - Rohit Jain
1
@D.R. 我理解对于初学者来说,泛型中的擦除会带来很多问题。由于我不使用C#,所以无法详细讨论比较方面的内容。但是,在我看来,实现这种方式的整个动机是为了避免JVM实现上的重大变化。 - Rohit Jain
1
为什么会“在运行时肯定失败”?正如你所说,没有(通用的)类型信息,因此运行时无法检查。如果通过错误的类型,它可能可能在运行时失败,但是对此没有任何“确定性”。 - Hot Licks
2
@RohitJain:我并不是在批评Java的泛型概念,但这个单一的后果仍然会产生丑陋的代码;-) - D.R.
1
@D.R. - Java的泛型从一开始就很丑陋。这主要是因为C++功能的渴望。 - Hot Licks
显示剩余3条评论

0

我发现当处理一个未经类型化的一类集合时,您可以创建一个带有类型的集合

UntypedObjCollection bag = some preGenerics method;
List<Foo> foolist = new ArrayList<>();
bag.stream().forEach(o ->fooList.add((Foo)o));

似乎没有一种方法可以将Object转换为某个类型并在一次操作中进行过滤、映射等操作。将Object放入一个类型化的集合中的唯一方法是使用终端操作。这是在运行JDK 17时处理module.base异常时尝试使用不太精细的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接