Java 8 Streams: создание многоуровневых / составных объектов

Я начинаю использовать java 8 stream API. Я хотел бы преобразовать список "набор результатов sql" для объектов домена, то есть составной структуры.

Объекты домена: у пользователя есть набор разрешений, у каждого разрешения - год набора приложений. Например, у Джона есть 2 разрешения (MODERATOR и DEV). его разрешение модератора применимо только на 2014 и 2015 годы, его разрешение на разработку применимо только на 2014 год.

class User {
  // some primitives attributes
  List<Permission> permission;
}

class Permission {
  // some primitives attributes
  List<Integer> years;
}

Теперь я делаю запрос и получаю список плоских результатов, что-то вроде:

[1, "moderator", 2014]
[1, "moderator", 2015]
[1, "dev", 2014]
[2, "dev", 2010]
[2, "dev", 2011]
[2, "dev", 2012]

1 и 2 - ИД пользователя.

Я пробовал различную конструкцию, но в конце она сложнее, чем беглая. И не сработало:)
Я читал в книге по Java 8, что "просто" создавать объекты dompain с помощью сборщиков.
Я немного заплакал, когда прочитал это:'(

Я старался

sb.collect(
    collectingAndThen(
        groupingBy(
            Mybean::getUserId,
            collectingAndThen(
                groupingBy(Monbean::getPermissionId, mapping(convertPermission, toList())),
                finisherFonction)
            ),
        convertUser)
);

и получил одну чертову ошибку компиляции дженериков.

  • Каков наилучший способ создания многоуровневых составных доменных объектов с использованием потоков Java 8?
  • Является ли collectionAndThen / finisher хорошей идеей?
  • или вы используете только groupingBy с последующей функцией отображения?
  • вы преобразовываете классификатор в объект (своего рода функция отображения первого уровня?)

Потому что в конце я хочу избавиться от карты и получить List<User> результат (я думаю, что я могу добавить вызов карты на entrySet, чтобы закончить преобразование).

2 ответа

Решение

Позвольте мне предложить вам несколько вариантов, и вы решите, какой из них вам наиболее понятен. Я предполагаю, что конструктор пользователя User(int userId, List<Permission> permissions) и конструктор разрешений Permission(String permissionId, List<Integer> years)

Вариант 1: прямой подход. Сгруппируйте по идентификатору пользователя, создайте список разрешений для каждого идентификатора пользователя и создайте объекты User. Лично я нахожу, что за этим большим количеством вложений в коллекционерах трудно следить.

List<User> users = beans.stream()
    .collect(
        groupingBy(
            MyBean::getUserid,
            collectingAndThen(
                groupingBy(
                    MyBean::getPermission,
                    mapping(MyBean::getYear, toList())
                ),
                t -> t.entrySet().stream()
                    .map(e -> new Permission(e.getKey(), e.getValue()))
                    .collect(toList())
            )
        )
    ).entrySet().stream()
    .map(e -> new User(e.getKey(), e.getValue()))
    .collect(toList());

Вариант 2: То же, что и выше, но для ясности сделайте сборщик разрешений отдельно.

Collector<MyBean, ?, List<Permission>> collectPermissions = collectingAndThen(
    groupingBy(MyBean::getPermission, mapping(MyBean::getYear, toList())),
    t -> t.entrySet().stream()
        .map(e -> new Permission(e.getKey(), e.getValue()))
        .collect(toList())
);

List<User> users = beans.stream()
    .collect(groupingBy(MyBean::getUserid, collectPermissions))
    .entrySet().stream()
    .map(e -> new User(e.getKey(), e.getValue()))
    .collect(toList());

Вариант 3: Сначала сверните бины в карту идентификатора пользователя, чтобы сопоставить разрешение и список лет (Map<Integer, Map<String, List<Integer>>). Затем создайте доменные объекты из карты.

List<User> users = beans.stream().collect(
    groupingBy(
        MyBean::getUserid,
        groupingBy(
            MyBean::getPermission,
            mapping(MyBean::getYear, toList())
        )
    )
).entrySet().stream()
    .map(u -> new User(
            u.getKey(),
            u.getValue().entrySet().stream()
                .map(p -> new Permission(p.getKey(), p.getValue()))
                .collect(toList())
        )
    ).collect(toList());

collectingAndThen Комбинатор предназначен для случаев, когда промежуточная форма накопления отличается от желаемой конечной формы. Это случай для joining() коллектор (где промежуточная форма представляет собой StringBuilder, но окончательная форма String), а также может использоваться для упаковки изменяемых коллекций неизменяемыми обертками после завершения сбора. Поэтому я не думаю, что это инструмент, который вы ищете.

Если вы ищете "разрешения для пользователя", это поможет:

Map<UserId, List<Permission>>
    queryResults.stream()
                .collect(qr -> qr.getId(), 
                         mapping(qr -> qr.getPermission() + ":" + qr.getDate(), 
                                 toList()));

Это приведет к:

1 -> [ moderator:2014, moderator:2015, dev:2014 ]
2 -> [ dev:2010, dev:2011, dev:2012 ]

Идея такова: - Вы группируете по "id" (первое поле вашего запроса) - Для каждой записи вы выбираете какую-то функцию из оставшихся полей (здесь я использовал для ясности строку concat, но вы могли бы создать объект, содержащий разрешение и год), и отправьте его нижестоящему коллектору - используйте Collectors.toList() в качестве нижестоящего коллектора, который затем будет "значением" Map создано groupingBy,

Другие вопросы по тегам