首页 文章

如何分组到数组的映射?

提问于
浏览
1

流上的 groupingBy 操作是否可以生成一个映射,其中值是数组而不是列表或其他一些集合类型?

例如:我有一个类 Thing . 事情有所有者,所以 Thing 有一个 getOwnerId 方法 . 在一系列事物中,我想按所有者ID对事物进行分组,以便具有相同所有者ID的事物最终在一个数组中 . 换句话说,我想要一个如下所示的 Map ,其中键是所有者ID,值是属于该所有者的事物的数组 .

Map<String, Thing[]> mapOfArrays;

在我的情况下,因为我需要将映射值传递给需要数组的库方法,所以收集到 Map<String, Thing[]> 最方便 .

将整个流收集到一个数组中很容易(它甚至不需要显式收集器):

Thing[] arrayOfThings = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
            .toArray(Thing[]::new);

[属于所有者1,属于所有者2,属于所有者1]

所有者ID的摸索也很容易 . 例如,要分组到列表中:

Map<String, List<Thing>> mapOfLists = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
            .collect(Collectors.groupingBy(Thing::getOwnerId));

{owner1 = [属于所有者1,属于所有者1],所有者2 = [属于所有者2]}

只有这个例子给我一张列表 Map . 有2-arg和3-arg groupingBy 方法可以给我一张其他集合类型的 Map (比如集合) . 我想,如果我可以将收集到一个数组的收集器(类似于上面第一个代码段中的数组中的集合)传递给两个arg Collectors.groupingBy(Function<? super T,? extends K>, Collector<? super T,A,D>) ,我就会被设置 . 但是, Collectors 类中的预定义收集器似乎都没有对数组执行任何操作 . 我错过了一个不太复杂的方式吗?

为了完整的例子,这里是我在上面的片段中使用的类:

public class Thing {

    private String ownerId;

    public Thing(String ownerId) {
        this.ownerId = ownerId;
    }

    public String getOwnerId() {
        return ownerId;
    }

    @Override
    public String toString() {
        return "Belongs to " + ownerId;
    }

}

3 回答

  • 2

    您可以先分组,然后使用后续的toMap:

    Map<String, Thing[]> result = source.stream()
                    .collect(groupingBy(Thing::getOwnerId))
                    .entrySet()
                    .stream()
                    .collect(toMap(Map.Entry::getKey,
                            e -> e.getValue().toArray(new Thing[0])));
    
  • 4

    可能很明显但你可以通过以下方式完成:

    Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
                .collect(Collectors.toMap(
                        Thing::getOwnerId,
                        x -> new Thing[]{x},
                        (left, right) -> {
                            Thing[] newA = new Thing[left.length + right.length];
                            System.arraycopy(left, 0, newA, 0, left.length);
                            System.arraycopy(right, 0, newA, left.length, right.length);
                            return newA;
                        }
                ))
    
  • 2

    使用this answer by Thomas Pliakas中的收集器:

    Map<String, Thing[]> mapOfArrays = Stream.of(new Thing("owner1"), new Thing("owner2"), new Thing("owner1"))
                .collect(Collectors.groupingBy(Thing::getOwnerId,
                        Collectors.collectingAndThen(Collectors.toList(),
                                tl -> tl.toArray(new Thing[0]))));
    

    我们的想法是首先收集到一个列表(这是一个明显的想法,因为数组具有恒定的大小),然后在返回到收集器的分组之前转换为数组 . collectingAndThen 可以通过它所谓的终结者做到这一点 .

    要打印结果以供检查:

    mapOfArrays.forEach((k, v) -> System.out.println(k + '=' + Arrays.toString(v)));
    

    owner1 = [属于所有者1,属于所有者1]
    owner2 = [属于所有者2]

    Edit: 感谢Aomine的链接:使用 new Thing[0] 作为 toArray 的参数受到Arrays of Wisdom of the Ancients的启发 . 似乎在Intel CPU上最终使用 new Thing[0] 比使用 new Thing[tl.size()] 更快 . 我很惊讶 .

相关问题