簡體   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

我的問題基本上歸結為這個簡化的示例。 我有一個數據庫返回的數據,該數據庫在行中有一些重復的信息。

在此示例中,我列出了從數據庫返回的TeamRow對象。 我可以使用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));

但是,我實際上想要實現的是將TeamRow轉換為Team 這樣,id和name只代表一次,而玩家則存儲在Team對象的List中。 我可以通過在地圖上添加一個forEach來實現此目標,如下所示。

但是我一直在嘗試找出是否可以通過添加某種映射器或下游收集器來達到相同的結果。 與添加后續forEach帶來任何好處嗎? 例如:

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

使用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));
    }
});

之前我說過要通過創建一個原子轉換器函數來做到這一點:

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

它處理映射,您的流代碼成為一個非常清晰的步驟:

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

但是,我意識到,您也可以這樣做:

    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())
                )
        );

這樣,在分配List<Team> teams之前,您永遠不會有可訪問的可變集合。

您可以使用帶有自定義合並功能的toMap收集器。 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;
    }
}

現在您可以通過以下方式解決您的問題:

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();

您可以嘗試類似

    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()))));

使用collectingAndThen() ,可以使用將映射項映射到Team的函數。 l.get(0)應該不會失敗,因為列表中始終至少有一個條目。 我不確定這是否更簡潔,但至少它不使用foreach

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM