【问题标题】:Is it possible to cast a Stream in Java 8?是否可以在 Java 8 中转换流?
【发布时间】:2014-04-26 01:05:33
【问题描述】:

是否可以在 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 的新特性,并且很好奇这是否可能。

【问题讨论】:

  • 从 Java 运行时的角度来看,这两种 Stream 类型已经相同,因此不需要强制转换。诀窍是让它通过编译器。 (也就是说,假设这样做是有意义的。)

标签: java java-8 java-stream


【解决方案1】:

我发现在处理一个类的无类型集合时,你可以 用

创建一个类型化的集合
UntypedObjCollection bag = some preGenerics method;
List<Foo> foolist = new ArrayList<>();
bag.stream().forEach(o ->fooList.add((Foo)o));

似乎没有一种方法可以一次性将 Object 投射到某物中并进行过滤、映射等...。将 Object 放入类型化集合的唯一方法是终端操作。这是运行 JDK 17 并在尝试较少粒度的方法时处理 module.base 异常。

【讨论】:

    【解决方案2】:

    我认为没有开箱即用的方法。一个可能更清洁的解决方案是:

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

    或者,按照 cmets 中的建议,您可以使用 cast 方法 - 不过前者可能更容易阅读:

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

    【讨论】:

    • 这几乎就是我想要的。我想我忽略了在map 中将其转换为客户端会返回Stream&lt;Client&gt;。谢谢!
    • +1 有趣的新方法,尽管它们冒着进入新一代类型(水平而不是垂直)的意大利面条代码的风险
    • @LordOfThePigs 是的,虽然我不确定代码是否变得更清晰,但它确实有效。我已将这个想法添加到我的答案中。
    • 你可以“简化” instanceOf 过滤器:Stream.of(objects).filter(Client.class::isInstance).[...]
    • @T3rm1 当你使用原始类型时,很多事情变得更加困难......
    【解决方案3】:

    按照ggovan's answer 的思路,我这样做如下:

    /**
     * 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);
    

    【讨论】:

      【解决方案4】:

      聚会迟到了,但我认为这是一个有用的答案。

      flatMap 是最短的方法。

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

      如果oClient,则使用单个元素创建一个流,否则使用空流。然后这些流将被展平为Stream&lt;Client&gt;

      【讨论】:

      • 我试图实现这一点,但收到警告说我的班级“使用未经检查或不安全的操作”——这是意料之中的吗?
      • 不幸的是,是的。如果您使用if/else 而不是?: 运算符,则不会出现警告。请放心,您可以安全地取消警告。
      • 其实这比Stream.of(objects).filter(o-&gt;o instanceof Client).map(o -&gt; (Client)o)甚至Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast)还要长。
      【解决方案5】:

      这看起来有点难看。是否可以将整个流转换为不同的类型?喜欢将Stream&lt;Object&gt; 转换为Stream&lt;Client&gt;

      不,那是不可能的。这在 Java 8 中并不新鲜。这是特定于泛型的。 List&lt;Object&gt; 不是List&lt;String&gt; 的超类型,因此您不能只将List&lt;Object&gt; 转换为List&lt;String&gt;

      这里的问题也是类似的。您不能将Stream&lt;Object&gt; 转换为Stream&lt;Client&gt;。当然你可以像这样间接地转换它:

      Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;
      

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

      顺便说一句,你的方法有什么问题?我觉得很好。

      【讨论】:

      • @D.R. C# 中的泛型是使用具体化实现的,而在 Java 中,它是使用擦除实现的。两者都以不同的方式实现底层。所以你不能指望它在两种语言中都以同样的方式工作。
      • @D.R.我知道擦除给初学者理解Java中泛型的概念带来了很多问题。而且由于我不使用 C#,因此无法详细介绍比较。但以这种方式实现 IMO 背后的全部动机是避免 JVM 实现发生重大变化。
      • 为什么它“在运行时肯定会失败”?正如您所说,没有(通用)类型信息,因此运行时无需检查。如果输入了错误的类型,它可能可能在运行时失败,但无论如何都没有“确定性”。
      • @RohitJain:我并不是在批评 Java 的通用概念,但这个单一的结果仍然会产生丑陋的代码 ;-)
      • @D.R. - 从 git-go 来看,Java 泛型很难看。大多数只是 C++ 功能的欲望。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-19
      • 1970-01-01
      • 2014-03-31
      • 1970-01-01
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      相关资源
      最近更新 更多