partitioningBy的目的是啥

Posted

技术标签:

【中文标题】partitioningBy的目的是啥【英文标题】:What's the purpose of partitioningBypartitioningBy的目的是什么 【发布时间】:2015-03-15 15:03:08 【问题描述】:

例如,如果我打算对一些元素进行分区,我可以这样做:

Stream.of("I", "Love", "Stack Overflow")
      .collect(Collectors.partitioningBy(s -> s.length() > 3))
      .forEach((k, v) -> System.out.println(k + " => " + v));

哪个输出:

false => [I]
true => [Love, Stack Overflow]

但对我来说partioningBy 只是groupingBy 的一个子案例。虽然前者接受Predicate 作为参数,而后者接受Function,但我只是将分区视为正​​常的分组功能。

所以同样的代码做同样的事情:

 Stream.of("I", "Love", "Stack Overflow")
       .collect(Collectors.groupingBy(s -> s.length() > 3))
       .forEach((k, v) -> System.out.println(k + " => " + v));

这也会导致Map<Boolean, List<String>>

那么有什么理由我应该使用partioningBy 而不是groupingBy?谢谢

【问题讨论】:

【参考方案1】:

partitioningBy 将始终返回一个包含两个条目的映射,一个用于谓词为真,另一个用于谓词为假。 两个条目可能都有空列表,但它们会存在。

groupingBy 不会这样做,因为它只在需要时创建条目。

在极端情况下,如果您向partitioningBy 发送一个空流,您仍然会在映射中获得两个条目,而groupingBy 将返回一个空映射。

编辑:正如下面提到的,Java 文档中没有提到这种行为,但是改变它会带走 partitioningBy 当前提供的附加值。对于 Java 9,这已经在规范中。

【讨论】:

这是最合理的行为,但我看不到 javadocs 中两个条目的保证。我已经在***.com/questions/41287517/… 提出了一个问题。 @JoshuaTaylor 感谢您的信息!我更新了答案以包含来自其他线程的信息。【参考方案2】:

partitioningBy 的效率稍高一些,它使用了一个特殊的 Map 实现,针对密钥只是 boolean 时进行了优化。

(这也可能有助于澄清您的意思;partitioningBy 有助于有效地理解存在用于分区数据的布尔条件。)

【讨论】:

考虑到 Java API 普遍强烈偏好不包含便利的特殊情况方法,我有点惊讶的是,这是包含它的所有论证。一个小的性能优势和一个小的澄清。我看到 Doug Lea 的想法 :) 除此之外,还有一个小区别:如果所有元素都满足 Predict,则 partitioningBy 结果仍然包含一个到空列表的假键映射,而 groupingBy 结果不会有假键. “稍微高效”的实现在每次调用 get 时创建 6 个对象。只是因为他们没有费心用简单的实现return key ? forTrue : forFalse 覆盖get @MGhostSoft 这种行为使得 partitioningBy 在某些情况下优于分组。但是,虽然实验证实了这种行为,但我没有在文档中看到它。这似乎是合理的,但我对依赖未指定的行为犹豫不决。你知道关于包含真假条目的结果的任何保证吗?这是最合理的行为,但我看不到 javadocs 中两个条目的保证。我已经在***.com/questions/41287517/… 提出了一个问题。 @Mghostsoft 看看我链接到的问题,答案说保证是在 Java 9 的 javadoc 中添加的。【参考方案3】:

partitioningBy 方法将返回一个映射,其键始终为布尔值,但在 groupingBy 方法的情况下,键可以是任何 Object 类型

//groupingBy
Map<Object, List<Person>> list2 = new HashMap<Object, List<Person>>();
list2 = list.stream().collect(Collectors.groupingBy(p->p.getAge()==22));
System.out.println("grouping by age -> " + list2);

//partitioningBy
Map<Boolean, List<Person>> list3 = new HashMap<Boolean, List<Person>>();
list3 = list.stream().collect(Collectors.partitioningBy(p->p.getAge()==22));
System.out.println("partitioning by age -> " + list2);

如您所见,在partitioningBy方法的情况下map的key总是一个布尔值,但是在groupingBy方法的情况下,key是Object类型

详细代码如下:

    class Person 
    String name;
    int age;

    Person(String name, int age) 
        this.name = name;
        this.age = age;
    

    public String getName() 
        return name;
    

    public int getAge() 
        return age;
    

    public String toString() 
        return this.name;
    


public class CollectorAndCollectPrac 
    public static void main(String[] args) 
        Person p1 = new Person("Kosa", 21);
        Person p2 = new Person("Saosa", 21);
        Person p3 = new Person("Tiuosa", 22);
        Person p4 = new Person("Komani", 22);
        Person p5 = new Person("Kannin", 25);
        Person p6 = new Person("Kannin", 25);
        Person p7 = new Person("Tiuosa", 22);
        ArrayList<Person> list = new ArrayList<>();
        list.add(p1);
        list.add(p2);
        list.add(p3);
        list.add(p4);
        list.add(p5);
        list.add(p6);
        list.add(p7);

        // groupingBy
        Map<Object, List<Person>> list2 = new HashMap<Object, List<Person>>();
        list2 = list.stream().collect(Collectors.groupingBy(p -> p.getAge() == 22));
        System.out.println("grouping by age -> " + list2);

        // partitioningBy
        Map<Boolean, List<Person>> list3 = new HashMap<Boolean, List<Person>>();
        list3 = list.stream().collect(Collectors.partitioningBy(p -> p.getAge() == 22));
        System.out.println("partitioning by age -> " + list2);

    

【讨论】:

【参考方案4】:

groupingBypartitioningBy 之间的另一个区别是前者采用Function&lt;? super T, ? extends K&gt;,而后者采用Predicate&lt;? super T&gt;

当你传递一个方法引用或者一个lambda表达式,比如s -&gt; s.length() &gt; 3,它们可以被这两种方法中的任何一种使用(编译器会根据你选择的方法所需要的类型来推断函数接口类型) .

但是,如果您有 Predicate&lt;T&gt; 实例,则只能将其传递给 Collectors.partitioningBy()Collectors.groupingBy()不会接受它。

同样,如果你有一个Function&lt;T,Boolean&gt; 实例,你只能将它传递给Collectors.groupingBy()Collectors.partitioningBy() 不会接受它。

【讨论】:

【参考方案5】:

如其他答案所示,将集合分为两组在某些情况下很有用。由于这两个分区将始终存在,因此更容易进一步利用它。在JDK中,为了隔离所有的类文件和配置文件,使用partitioningBy

    private static final String SERVICES_PREFIX = "META-INF/services/";
    
    // scan the names of the entries in the JAR file
    Map<Boolean, Set<String>> map = jf.versionedStream()
            .filter(e -> !e.isDirectory())
            .map(JarEntry::getName)
            .filter(e -> (e.endsWith(".class") ^ e.startsWith(SERVICES_PREFIX)))
            .collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX),
                                               Collectors.toSet()));

    Set<String> classFiles = map.get(Boolean.FALSE);
    Set<String> configFiles = map.get(Boolean.TRUE);

代码 sn-p 来自jdk.internal.module.ModulePath#deriveModuleDescriptor

【讨论】:

以上是关于partitioningBy的目的是啥的主要内容,如果未能解决你的问题,请参考以下文章

Java8 中 Collectors 中 partitioningBy 与 groupingBy 区别

Docker,它是啥,目的是啥

AsQueryable() 的目的是啥?

@ConditionalOnProperty 注释的目的是啥?

toBeFalsy() 的目的是啥?

移位的目的是啥? [关闭]