【问题标题】:Java 8 - omitting tedious collect methodJava 8 - 省略了繁琐的收集方法
【发布时间】:2016-09-04 13:57:00
【问题描述】:

Java 8 流 api 是一个非常好的功能,我非常喜欢它。让我紧张的一件事是,90% 的时间我都希望将输入作为集合,将输出作为集合。结果是我必须一直调用stream()collect() 方法:

collection.stream().filter(p->p.isCorrect()).collect(Collectors.toList());

是否有任何 java api 可以让我跳过流并直接对集合进行操作(如 c# 中的linq?):

collection.filter(p->p.isCorrect)

【问题讨论】:

  • 请注意,(p->p.isCorrect()) 写成(MyClass::isCorrect) 更正确。
  • @BoristheSpider,为什么更正确?
  • @AndrewTobilko 一个 lambda 可能需要创建一个 static 方法,或者最坏的情况是一个匿名的 class。使用方法引用意味着这些都不是必需的。
  • @BoristheSpider:我刚刚测试过,方法引用似乎会导致匿名类与 lambda 完全一样。 (具体来说,我写了System.out.println(((Callable<String>) this::toString).getClass())System.out.println(((Callable<String>) () -> toString()).getClass()),它们都打印了class com.example.Foo$$Lambda$1/13451323 之类的东西。)你能解释一下为什么你认为它们不同吗?
  • @Boris the Spider:运行时生成的类没有区别。由于在方法参考案例中没有生成的方法,效率上只有轻微的差异,但这种轻微的性能差异不允许将这些备选方案中的任何一个归类为“更正确”。

标签: java java-8 java-stream


【解决方案1】:

使用cyclops-react,您有多种选择。

我们可以利用惰性扩展集合

  CollectionX<String> collection = ListX.of("hello","world");
  CollectionX<String> filtered = collection.filter(p->p.isCorrect());

支持可变、不可变和持久的扩展集合。集合上的函数操作是惰性的(即表现得像可重放的流),并且仅在首次访问时实现。

我们可以利用强大的扩展 Stream 类型

  ReactiveSeq.fromIterable(collection)
             .filter(p->p.isCorrect())
             .toList();

[披露我是 cyclops-react 的主要开发者]

【讨论】:

    【解决方案2】:

    如果你想对集合进行操作Guava's FluentIterable是一种方法!

    示例(获取 10 位第一个 vip 客户的 ID):

    FluentIterable
           .from(customers)
           .filter(customer -> customer.isVIP())
           .transform(Client::getId)
           .limit(10);
    

    【讨论】:

    • 这并不比使用 Streams 更好。它返回一个Iterable,而不是Collection,这可以用Iterables.filter() 更简洁地完成。
    • 你是对的。然而,我发现 toList() 方法比流的 collect(Collectors.toList()) 更令人愉快。
    • @user3364192 如果您对Collectors.* 使用静态导入,则流版本会稍微简洁一些。
    • @user3364192 不仅没有比基于Stream 的解决方案更好,事实上不够简洁。但它也需要一个外部库,并且提供的功能比Stream 少得多。我真的看不出这怎么可能是一个更好的解决方案......
    【解决方案3】:

    如果您需要过滤视图而不修改原始集合,请考虑 Guava 的Collections2.filter()

    【讨论】:

    • @user3364192 请注意,这显然不一样。 filter() 返回一个view,因此Predicate 将在每次迭代/访问时进行评估,get size 等变为O(n) 操作而不是O(1)(对于ArrayList)。此处提出的三种解决方案并不等同——它们各有优缺点。
    【解决方案4】:

    如果您愿意使用第三方库,您可以使用Eclipse Collections,它具有丰富的 API,可直接在集合下使用。您的示例可以使用 Eclipse Collections 编写如下。

    collection.select(p->p.isCorrect)
    collection.select(MyClass::isCorrect)
    

    注意:我是 Eclipse Collections 的提交者。

    【讨论】:

      【解决方案5】:

      是的,有几个库可以解决 Java 8 的流冗长问题。不完整的列表:

      我更喜欢 jOOL。我在上一个项目中一直在使用它。其他我知道但我没有真正使用过,所以我不能给你一个印象。

      您使用 jOOL 的示例是:

      Seq.seq(collection).filter(p->p.isCorrect()).toList();
      

      【讨论】:

        【解决方案6】:

        我也认为 Stream API 很好,但对于简短的操作来说很冗长。我在几个项目中使用了这些实用方法:

        import java.util.List;
        import java.util.function.Function;
        import java.util.function.Predicate;
        import java.util.stream.Collectors;
        import java.util.stream.Stream;
        
        public class Functions {
        
            public static <T,V> List<V> map(final List<T> in, final Function<T, V> function) {
                return in == null ? null : map(in.stream(), function);
            }
        
            public static <T,V> List<V> map(final Stream<T> in, final Function<T, V> function) {
                return in == null ? null : in
                    .map(function)
                    .collect(Collectors.toList());
            }
        
            public static <T> List<T> filter(final List<T> in, final Predicate<T> predicate) {
                return in == null ? null : filter(in.stream(), predicate);
            }
        
            public static <T> List<T> filter(final Stream<T> in, final Predicate<T> predicate) {
                return in == null ? null : in
                    .filter(predicate)
                    .collect(Collectors.toList());
            }
        }
        

        这让我可以做例如

        List<String> wrapped = Functions.map(myList, each -> "[" + each + "]");
        

        通常我也会静态导入该方法。

        【讨论】:

          【解决方案7】:

          是的,使用Collection#removeIf(Predicate)

          删除此集合中满足给定谓词的所有元素。

          请注意,它会更改给定的集合,而不是返回一个新集合。但是您可以创建集合的副本并对其进行修改。另请注意,谓词需要被否定才能充当过滤器:

          public static <E> Collection<E> getFilteredCollection(Collection<E> unfiltered,
                                                                Predicate<? super E> filter) {
              List<E> copyList = new ArrayList<>(unfiltered);
          
              // removeIf takes the negation of filter 
              copyList.removeIf(e -> { return !filter.test(e);});  
          
              return copyList;
          }
          

          但正如@Holger 在 cmets 中所建议的那样,如果您选择在代码中定义此实用程序方法并在需要获得过滤集合的任何地方使用它,那么只需将调用委托给该实用程序中的 collect 方法即可。你的调用者代码会更简洁。

          public static <E> Collection<E> getFilteredCollection(Collection<E> unfiltered,
                                                                Predicate<? super E> filter) {
             return unfiltered.stream()
                              .filter(filter)
                              .collect(Collectors.toList());
          }
          

          【讨论】:

          • 但请记住,谓词是否定的(与 filter 相比)并且 removeIf 修改了原始集合并且不返回新集合。
          • copyList.removeIf(filter.negate());
          • 知道从 Java 9 开始您将能够执行 collection.stream().collect(filtering(p -&gt; p.isCorrect(), toList()) 可能会很有趣。这不是filtering 收集器的真正意义,但我认为我宁愿使用它而不是创建方法。
          • @Jean-FrançoisSavard,这比collection.stream().filter(p -&gt; p.isCorrect()).collect(toList()) 好多少?
          • 当您将操作隐藏在一个实用方法 ala getFilteredCollection 中时,使用 removeIf 是没有意义的,因为您可以简单地说 return unfiltered.stream().filter(filter) .collect(Collectors.toList()); 对调用者没有区别。而且它比先复制整个列表然后删除项目更短更有效。
          【解决方案8】:

          你可以试试这个,来自 guava 库。它似乎比 Stream 方法更简洁。

           ImmutableList.copyOf(Iterables.filter(collection, MyClass::isCorrect));
          

          有关该技术的讨论,请参阅 Google Collections (Guava Libraries): ImmutableSet/List/Map and Filtering

          【讨论】:

            【解决方案9】:

            您可能喜欢使用StreamEx

            StreamEx.of(collection).filter(PClass::isCorrect).toList();
            

            这样的优点是在保持不变性的同时稍微简短一些。

            【讨论】:

            • 我想提到 StreamEx,但在这种情况下它提供的很少。
            • 它提供了一个更接近 C# API 的 stream API,这可能是 OP 正在搜索的内容。
            【解决方案10】:

            Streams 有一个定义明确的架构,which you can read a lot about。在你开始这条路之前,你可能想了解一下。

            但是为什么不实现一个集合,它实现了一个类似的流接口,为你包装了代码呢?

            public class StreamableCollection implements Collection, Stream {
            ...
            }
            

            然后你可以为你的用例做一些棘手的假设。你仍然可以从集合界面打开一个流,但你也可以直接跳进去,然后在我想的那个手柄的内部打开流。

                streamableCollection cs = new streamableCollection();
                cs.filter();
                cs.stream();
            

            您的 IDE 将让您正确实现所有内容...只需将所有内容传回默认实现即可。

            【讨论】:

            • 我想你会想要一个定义stream() 方法的Streamable 接口并实现它,而不是Stream。这类似于IterableIteratorStreamIterator 都被消耗且不可重用,因此 *able 接口通过为每次想要再次运行它定义一个工厂方法来解决这个问题。
            • StreamableCollectionfilter 会做什么?如果它修改了底层集合,那就是一个问题。如果没有,那就是一种观点,这是一个问题。我不相信你可以明智地实现这一点。
            • 当然,这是@HankD 的一个快速概念。好的想法,肯定会清理模式。
            • @BoristheSpider 我认为这就是为什么 Streams 没有像 OP 想要的那样实现的原因。您可以为他们的用例拼凑一些东西,但我不确定这是一个好主意。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-11-19
            相关资源
            最近更新 更多