简体   繁体   English

如何使用Java 8 Streams分组通过将多个数据库记录映射到具有列表的单个对象 <String> 一栏属性

[英]How to use Java 8 Streams groupingBy to map multiple database records to a single object with a List<String> property for one column

My problem essentially comes down to this simplified example. 我的问题基本上归结为这个简化的示例。 I have data coming back from a DB which has some duplicate information in the rows. 我有一个数据库返回的数据,该数据库在行中有一些重复的信息。

In this example I have a list of TeamRow objects that come back from the DB. 在此示例中,我列出了从数据库返回的TeamRow对象。 I can easily group these using Collectors.groupingBy : 我可以使用Collectors.groupingBy轻松将它们分组:

public class TeamRow {
    private int id;
    private String name;
    private String player;

    public TeamRow(int id, String name, String player) {
        this.id = id;
        this.name = name;
        this.player = player;
    }

    public int getId() {return id;}
    public String getName() { return name; }
    public String getPlayer() {return player;}
}

public class Team {
    private int id;
    private String name;
    private List<String> players;

    public Team(int id, String name, List<String> players) {
        this.id = id;
        this.name = name;
        this.players = new ArrayList<String>(players);
    }
}

List<TeamRow> dbTeams = new ArrayList<TeamRow>();
dbTeams.add(new TeamRow(1, "Team1", "Jonny"));
dbTeams.add(new TeamRow(1, "Team1", "Rob"));
dbTeams.add(new TeamRow(1, "Team1", "Carlos"));
dbTeams.add(new TeamRow(2, "Team2", "Shane"));
dbTeams.add(new TeamRow(2, "Team2", "Lucas"));
dbTeams.add(new TeamRow(3, "Team3", "Geraint"));
dbTeams.add(new TeamRow(3, "Team3", "Rocky"));
dbTeams.add(new TeamRow(3, "Team3", "Wayne"));
dbTeams.add(new TeamRow(3, "Team3", "Dwayne"));
dbTeams.add(new TeamRow(3, "Team3", "Lester"));

Map<Integer, List<TeamRow>> myMap = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId));

However, what I'm actually trying to achieve is to convert the TeamRow s to Team s. 但是,我实际上想要实现的是将TeamRow转换为Team So that the id and name are only represented once and the players are stored in a List in the Team object. 这样,id和name只代表一次,而玩家则存储在Team对象的List中。 I can achieve this by adding a forEach over the map as shown below. 我可以通过在地图上添加一个forEach来实现此目标,如下所示。

But I've been trying to figure out if there is a way I can achieve the same result by adding some sort of mapper or downstream collector. 但是我一直在尝试找出是否可以通过添加某种映射器或下游收集器来达到相同的结果。 Would this even offer any benefit over adding a subsequent forEach ?? 与添加后续forEach带来任何好处吗? Eg: 例如:

List<Team> teams = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId, ???), ???).???; 

Conversion using forEach: 使用forEach进行转换:

List<Team> teams = new ArrayList<>();
myMap.forEach((id, teamRows) -> {
    if (teamRows.size() > 0) {
        TeamRow tr = teamRows.get(0);
        List<String> players = teamRows.stream().map(TeamRow::getPlayer).collect(Collectors.toList());
        teams.add(new Team(id, tr.getName(), players));
    }
});

Previously I said I would do it by creating an atomic transformer function like this: 之前我说过要通过创建一个原子转换器函数来做到这一点:

Function<TeamRow, Team> getTeamRowTransformer() {
    final Map<Integer, Team> map = new ConcurrentHashMap<Integer, Team>();
    return (teamRow)->{
        Team result = map.computeIfAbsent(teamRow.getId(), id->new Team(id, teamRow.getName(), Collections.emptyList()));
        result.players.add(teamRow.getPlayer());
        return result;
    };
}

It handles the mapping and your stream code becomes one very legible step: 它处理映射,您的流代码成为一个非常清晰的步骤:

Set<Team> finalTeams = dbTeams.stream()
    .map(getTeamRowTransformer())
    .collect(Collectors.toSet());

HOWEVER, I realized, you can also do this: 但是,我意识到,您也可以这样做:

    List<Team> teams = dbTeams.stream()
        .map(tr->new Team(tr.getId(), tr.getName(), Arrays.asList(tr.getPlayer())))
        .collect(Collectors.collectingAndThen(
                    Collectors.groupingBy(t->t.id, 
                        Collectors.reducing((Team a, Team b)->{
                            a.players.addAll(b.players); 
                            return (Team)a;
                        })
                    ), m->m.values().stream()
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .collect(Collectors.toList())
                )
        );

This way you never have an accessible mutable collection until List<Team> teams is assigned. 这样,在分配List<Team> teams之前,您永远不会有可访问的可变集合。

You may use toMap collector with custom merge function. 您可以使用带有自定义合并功能的toMap收集器。 It's probably a good idea to add merge method to the Team class: merge方法添加到Team类中可能是一个好主意:

public class Team {
    private final int id;
    private final String name;
    private final List<String> players;

    public Team(int id, String name, List<String> players) {
        this.id = id;
        this.name = name;
        this.players = new ArrayList<>(players);
    }

    // merges other team into this team, returning this team
    public Team merge(Team other) {
        assert id == other.id; // remove asserts if you don't like them
        assert name.equals(other.name);
        players.addAll(other.players);
        return this;
    }
}

Now you can solve your problem this way: 现在您可以通过以下方式解决您的问题:

Collection<Team> teams = dbTeams.stream()
        .map(tr -> new Team(tr.id, tr.name, Arrays.asList(tr.player)))
        .collect(Collectors.toMap(t -> t.id, t -> t, Team::merge)).values();

You could try something like 您可以尝试类似

    List<Team> teamList = dbTeams.stream().collect(Collectors.collectingAndThen(Collectors.groupingBy(TeamRow::getId),
            (m -> m.entrySet().stream().map(
            e -> {
                List<TeamRow> l = e.getValue();
                return new Team(l.get(0).getId(), l.get(0).getName(), l.stream().map(TeamRow::getPlayer).collect(Collectors.toList()));

            }
    ).collect(Collectors.toList()))));

Using collectingAndThen() you can use a function which maps the entries of the map to Team s. 使用collectingAndThen() ,可以使用将映射项映射到Team的函数。 l.get(0) should not fail as there is always at least one entry in the list. l.get(0)应该不会失败,因为列表中始终至少有一个条目。 I am not sure if this is more concise, but at least it does not use foreach . 我不确定这是否更简洁,但至少它不使用foreach

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在 java 流中的 groupingBy 属性下 map 多个属性 - How to map multiple properties under groupingBy property in java streams Java8 Stream List <Map <String,Object >> groupingBy和count value - Java8 Stream List<Map<String,Object>> groupingBy and counting value Java 8 Streams - 使用流将相同类型的多个对象映射到列表 - Java 8 Streams - Map Multiple Object of same type to a list using streams Java 8 流:如何转换地图<String, List<Integer> &gt; 到地图<Integer, List<String> &gt; 使用 groupingBy(.) - Java 8 stream: how to Convert Map<String, List<Integer>> to Map<Integer, List<String>> using groupingBy(.) 如何在流中的对象列表上使用 groupingBy、排序和减少 - How to use groupingBy, sort and reduce on a List of objects in streams 如何使用 java 8 个流分组来计算过滤器的平均值 - how do I use java 8 streams groupingby to calculate average with filters 使用Java 8流分组在地图列表列表中? - Using Java 8 streams groupingBy on a list of list of maps? 使用Java 8流分组在字符串列表列表中? - Using Java 8 streams groupingBy on a list of list of Strings? 如何在Java8 Streams中通过多个过滤谓词比较两个Map列表以识别匹配和不匹配的记录 - How to compare Two List of Map to identify the matching and non matching records with multiple filter predicates in Java8 Streams How to map a Java class property that is an object to a single JPA table column? - How to map a Java class property that is an object to a single JPA table column?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM