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
,