Как сгруппировать в карту массивов? - PullRequest
0 голосов
/ 09 декабря 2018

Может ли операция groupingBy в потоке создать карту, в которой значения являются массивами , а не списками или каким-либо другим типом коллекции?

Например: у меня есть класс Thing.У вещей есть владельцы, поэтому Thing имеет метод getOwnerId.В потоке вещей я хочу сгруппировать вещи по идентификатору владельца, чтобы вещи с одинаковым идентификатором владельца попали в массив вместе.Другими словами, мне нужна карта, подобная следующей, где ключи - это идентификаторы владельца, а значения - это массивы вещей, принадлежащих этому владельцу.

    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]

Также легко найти идентификатор владельца.Например, чтобы сгруппировать в списки:

    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]}

Только этот пример дает мне карту списков .Существуют методы 2-arg и 3-arg groupingBy, которые могут дать мне карту других типов коллекций (например, наборов).Я подумал, что если я смогу передать сборщик, который собирает в массив (аналогично сбору в массив в первом фрагменте выше), в два аргумента 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 ]

0 голосов
/ 09 декабря 2018

Использование сборщика из этого ответа Томаса Плиакаса :

    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=[Belongs to owner1, Belongs to owner1]
owner2=[Belongs to owner2]

Редактировать: Благодаря Aomine за ссылку: Использование new Thing[0] в качестве аргумента для toArray было вдохновлено Массивом Древности .Похоже, что на процессорах Intel в конце концов использование new Thing[0] быстрее, чем использование new Thing[tl.size()].Я был удивлен.

0 голосов
/ 09 декабря 2018

Вероятно, очевидно, но вы могли бы сделать это через:

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;
                    }
            ))
0 голосов
/ 09 декабря 2018

вы можете сгруппировать сначала, а затем использовать следующую 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])));
...