How to groupBy object properties and map to another object using Java 8 Streams?












14















Suppose I have a group of bumper cars, which have a size, a color and an identifier ("car code") on their sides.



class BumperCar {
int size;
String color;
String carCode;
}


Now I need to map the bumper cars to a List of DistGroup objects, which each contains the properties size, color and a List of car codes.



class DistGroup {
int size;
Color color;
List<String> carCodes;

void addCarCodes(List<String> carCodes) {
this.carCodes.addAll(carCodes);
}
}


For example,



[
BumperCar(size=3, color=yellow, carCode=Q4M),
BumperCar(size=3, color=yellow, carCode=T5A),
BumperCar(size=3, color=red, carCode=6NR)
]


should result in:



[
DistGroup(size=3, color=yellow, carCodes=[ Q4M, T5A ]),
DistGroup(size=3, color=red, carCodes=[ 6NR ])
]


I tried the following, which actually does what I want it to do. But the problem is that it materializes the immediate result (into a Map) and I also think that it can be done at once (perhaps using mapping or collectingAndThen or reducing or something), resulting in more elegant code.



List<BumperCar> bumperCars = ...
Map<SizeColorCombination, List<BumperCar>> map = bumperCars.stream()
.collect(groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())));

List<DistGroup> distGroups = map.entrySet().stream()
.map(t -> {
DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
d.addCarCodes(t.getValue().stream()
.map(BumperCar::getCarCode)
.collect(toList()));
return d;
})
.collect(toList());


How can I get the desired result without using a variable for an immediate result?



Edit: How can I get the desired result without materializing the immediate result? I am merely looking for a way which does not materialize the immediate result, at least not on the surface. That means that I prefer not to use something like this:



something.stream()
.collect(...) // Materializing
.stream()
.collect(...); // Materializing second time


Of course, if this is possible.





Note that I omitted getters and constructors for brevity. You may also assume that equals and hashCode methods are properly implemented. Also note that I'm using the SizeColorCombination which I use as group-by key. This class obviously contains the properties size and color. Classes like Tuple or Pair or any other class representing a combination of two arbitrary values may also be used.
Edit: Also note that an ol' skool for loop can be used instead of course, but that is not in the scope of this question.










share|improve this question




















  • 2





    Just as a side note, groupingBy() does group the values into a List by default so toList() may be omitted

    – Lino
    Jan 18 at 12:35






  • 1





    The idea of using streams is to make code more readable, more self-explanatory (at the cost of performance), or massively parallelizable without boilerplate code. It's not a modern one-solution-fits-all replacement of the old ways. The code you provided is cryptic at best. I suggest using a classic for-loop which is much cleaner in this case.

    – Mark Jeronimus
    Jan 18 at 12:37













  • @Lino You're right. I removed it.

    – MC Emperor
    Jan 18 at 12:53











  • @MarkJeronimus That's right, that's why I'm not satisfied with my current solution and looking for an elegant way to achieve just the same result—if it exists. Otherwise I will gladly revert to the classic loop.

    – MC Emperor
    Jan 18 at 12:58
















14















Suppose I have a group of bumper cars, which have a size, a color and an identifier ("car code") on their sides.



class BumperCar {
int size;
String color;
String carCode;
}


Now I need to map the bumper cars to a List of DistGroup objects, which each contains the properties size, color and a List of car codes.



class DistGroup {
int size;
Color color;
List<String> carCodes;

void addCarCodes(List<String> carCodes) {
this.carCodes.addAll(carCodes);
}
}


For example,



[
BumperCar(size=3, color=yellow, carCode=Q4M),
BumperCar(size=3, color=yellow, carCode=T5A),
BumperCar(size=3, color=red, carCode=6NR)
]


should result in:



[
DistGroup(size=3, color=yellow, carCodes=[ Q4M, T5A ]),
DistGroup(size=3, color=red, carCodes=[ 6NR ])
]


I tried the following, which actually does what I want it to do. But the problem is that it materializes the immediate result (into a Map) and I also think that it can be done at once (perhaps using mapping or collectingAndThen or reducing or something), resulting in more elegant code.



List<BumperCar> bumperCars = ...
Map<SizeColorCombination, List<BumperCar>> map = bumperCars.stream()
.collect(groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())));

List<DistGroup> distGroups = map.entrySet().stream()
.map(t -> {
DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
d.addCarCodes(t.getValue().stream()
.map(BumperCar::getCarCode)
.collect(toList()));
return d;
})
.collect(toList());


How can I get the desired result without using a variable for an immediate result?



Edit: How can I get the desired result without materializing the immediate result? I am merely looking for a way which does not materialize the immediate result, at least not on the surface. That means that I prefer not to use something like this:



something.stream()
.collect(...) // Materializing
.stream()
.collect(...); // Materializing second time


Of course, if this is possible.





Note that I omitted getters and constructors for brevity. You may also assume that equals and hashCode methods are properly implemented. Also note that I'm using the SizeColorCombination which I use as group-by key. This class obviously contains the properties size and color. Classes like Tuple or Pair or any other class representing a combination of two arbitrary values may also be used.
Edit: Also note that an ol' skool for loop can be used instead of course, but that is not in the scope of this question.










share|improve this question




















  • 2





    Just as a side note, groupingBy() does group the values into a List by default so toList() may be omitted

    – Lino
    Jan 18 at 12:35






  • 1





    The idea of using streams is to make code more readable, more self-explanatory (at the cost of performance), or massively parallelizable without boilerplate code. It's not a modern one-solution-fits-all replacement of the old ways. The code you provided is cryptic at best. I suggest using a classic for-loop which is much cleaner in this case.

    – Mark Jeronimus
    Jan 18 at 12:37













  • @Lino You're right. I removed it.

    – MC Emperor
    Jan 18 at 12:53











  • @MarkJeronimus That's right, that's why I'm not satisfied with my current solution and looking for an elegant way to achieve just the same result—if it exists. Otherwise I will gladly revert to the classic loop.

    – MC Emperor
    Jan 18 at 12:58














14












14








14








Suppose I have a group of bumper cars, which have a size, a color and an identifier ("car code") on their sides.



class BumperCar {
int size;
String color;
String carCode;
}


Now I need to map the bumper cars to a List of DistGroup objects, which each contains the properties size, color and a List of car codes.



class DistGroup {
int size;
Color color;
List<String> carCodes;

void addCarCodes(List<String> carCodes) {
this.carCodes.addAll(carCodes);
}
}


For example,



[
BumperCar(size=3, color=yellow, carCode=Q4M),
BumperCar(size=3, color=yellow, carCode=T5A),
BumperCar(size=3, color=red, carCode=6NR)
]


should result in:



[
DistGroup(size=3, color=yellow, carCodes=[ Q4M, T5A ]),
DistGroup(size=3, color=red, carCodes=[ 6NR ])
]


I tried the following, which actually does what I want it to do. But the problem is that it materializes the immediate result (into a Map) and I also think that it can be done at once (perhaps using mapping or collectingAndThen or reducing or something), resulting in more elegant code.



List<BumperCar> bumperCars = ...
Map<SizeColorCombination, List<BumperCar>> map = bumperCars.stream()
.collect(groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())));

List<DistGroup> distGroups = map.entrySet().stream()
.map(t -> {
DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
d.addCarCodes(t.getValue().stream()
.map(BumperCar::getCarCode)
.collect(toList()));
return d;
})
.collect(toList());


How can I get the desired result without using a variable for an immediate result?



Edit: How can I get the desired result without materializing the immediate result? I am merely looking for a way which does not materialize the immediate result, at least not on the surface. That means that I prefer not to use something like this:



something.stream()
.collect(...) // Materializing
.stream()
.collect(...); // Materializing second time


Of course, if this is possible.





Note that I omitted getters and constructors for brevity. You may also assume that equals and hashCode methods are properly implemented. Also note that I'm using the SizeColorCombination which I use as group-by key. This class obviously contains the properties size and color. Classes like Tuple or Pair or any other class representing a combination of two arbitrary values may also be used.
Edit: Also note that an ol' skool for loop can be used instead of course, but that is not in the scope of this question.










share|improve this question
















Suppose I have a group of bumper cars, which have a size, a color and an identifier ("car code") on their sides.



class BumperCar {
int size;
String color;
String carCode;
}


Now I need to map the bumper cars to a List of DistGroup objects, which each contains the properties size, color and a List of car codes.



class DistGroup {
int size;
Color color;
List<String> carCodes;

void addCarCodes(List<String> carCodes) {
this.carCodes.addAll(carCodes);
}
}


For example,



[
BumperCar(size=3, color=yellow, carCode=Q4M),
BumperCar(size=3, color=yellow, carCode=T5A),
BumperCar(size=3, color=red, carCode=6NR)
]


should result in:



[
DistGroup(size=3, color=yellow, carCodes=[ Q4M, T5A ]),
DistGroup(size=3, color=red, carCodes=[ 6NR ])
]


I tried the following, which actually does what I want it to do. But the problem is that it materializes the immediate result (into a Map) and I also think that it can be done at once (perhaps using mapping or collectingAndThen or reducing or something), resulting in more elegant code.



List<BumperCar> bumperCars = ...
Map<SizeColorCombination, List<BumperCar>> map = bumperCars.stream()
.collect(groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())));

List<DistGroup> distGroups = map.entrySet().stream()
.map(t -> {
DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
d.addCarCodes(t.getValue().stream()
.map(BumperCar::getCarCode)
.collect(toList()));
return d;
})
.collect(toList());


How can I get the desired result without using a variable for an immediate result?



Edit: How can I get the desired result without materializing the immediate result? I am merely looking for a way which does not materialize the immediate result, at least not on the surface. That means that I prefer not to use something like this:



something.stream()
.collect(...) // Materializing
.stream()
.collect(...); // Materializing second time


Of course, if this is possible.





Note that I omitted getters and constructors for brevity. You may also assume that equals and hashCode methods are properly implemented. Also note that I'm using the SizeColorCombination which I use as group-by key. This class obviously contains the properties size and color. Classes like Tuple or Pair or any other class representing a combination of two arbitrary values may also be used.
Edit: Also note that an ol' skool for loop can be used instead of course, but that is not in the scope of this question.







java java-8 java-stream grouping collectors






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 22 hours ago







MC Emperor

















asked Jan 18 at 12:29









MC EmperorMC Emperor

8,209125388




8,209125388








  • 2





    Just as a side note, groupingBy() does group the values into a List by default so toList() may be omitted

    – Lino
    Jan 18 at 12:35






  • 1





    The idea of using streams is to make code more readable, more self-explanatory (at the cost of performance), or massively parallelizable without boilerplate code. It's not a modern one-solution-fits-all replacement of the old ways. The code you provided is cryptic at best. I suggest using a classic for-loop which is much cleaner in this case.

    – Mark Jeronimus
    Jan 18 at 12:37













  • @Lino You're right. I removed it.

    – MC Emperor
    Jan 18 at 12:53











  • @MarkJeronimus That's right, that's why I'm not satisfied with my current solution and looking for an elegant way to achieve just the same result—if it exists. Otherwise I will gladly revert to the classic loop.

    – MC Emperor
    Jan 18 at 12:58














  • 2





    Just as a side note, groupingBy() does group the values into a List by default so toList() may be omitted

    – Lino
    Jan 18 at 12:35






  • 1





    The idea of using streams is to make code more readable, more self-explanatory (at the cost of performance), or massively parallelizable without boilerplate code. It's not a modern one-solution-fits-all replacement of the old ways. The code you provided is cryptic at best. I suggest using a classic for-loop which is much cleaner in this case.

    – Mark Jeronimus
    Jan 18 at 12:37













  • @Lino You're right. I removed it.

    – MC Emperor
    Jan 18 at 12:53











  • @MarkJeronimus That's right, that's why I'm not satisfied with my current solution and looking for an elegant way to achieve just the same result—if it exists. Otherwise I will gladly revert to the classic loop.

    – MC Emperor
    Jan 18 at 12:58








2




2





Just as a side note, groupingBy() does group the values into a List by default so toList() may be omitted

– Lino
Jan 18 at 12:35





Just as a side note, groupingBy() does group the values into a List by default so toList() may be omitted

– Lino
Jan 18 at 12:35




1




1





The idea of using streams is to make code more readable, more self-explanatory (at the cost of performance), or massively parallelizable without boilerplate code. It's not a modern one-solution-fits-all replacement of the old ways. The code you provided is cryptic at best. I suggest using a classic for-loop which is much cleaner in this case.

– Mark Jeronimus
Jan 18 at 12:37







The idea of using streams is to make code more readable, more self-explanatory (at the cost of performance), or massively parallelizable without boilerplate code. It's not a modern one-solution-fits-all replacement of the old ways. The code you provided is cryptic at best. I suggest using a classic for-loop which is much cleaner in this case.

– Mark Jeronimus
Jan 18 at 12:37















@Lino You're right. I removed it.

– MC Emperor
Jan 18 at 12:53





@Lino You're right. I removed it.

– MC Emperor
Jan 18 at 12:53













@MarkJeronimus That's right, that's why I'm not satisfied with my current solution and looking for an elegant way to achieve just the same result—if it exists. Otherwise I will gladly revert to the classic loop.

– MC Emperor
Jan 18 at 12:58





@MarkJeronimus That's right, that's why I'm not satisfied with my current solution and looking for an elegant way to achieve just the same result—if it exists. Otherwise I will gladly revert to the classic loop.

– MC Emperor
Jan 18 at 12:58












4 Answers
4






active

oldest

votes


















4














If we assume that DistGroup has hashCode/equals based on size and color, you could do it like this:



bumperCars
.stream()
.map(x -> {
List<String> list = new ArrayList<>();
list.add(x.getCarCode());
return new SimpleEntry<>(x, list);
})
.map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))
.collect(Collectors.toMap(
Function.identity(),
Function.identity(),
(left, right) -> {
left.getCarCodes().addAll(right.getCarCodes());
return left;
}))
.values(); // Collection<DistGroup>





share|improve this answer


























  • Thanks, this code works for me. Note that I, after using this code, have tweaked the code a little, so the bumper cars are directly mapped to a DistGroup using .map(t -> new DistGroup(t.getSize(), t.getColor(), new ArrayList<>(Arrays.asList(t.getCarCode())))). Then I collect it with toMap with the same arguments as your code, but with the first argument being t -> new SimpleEntry<>(t.getColor(), t.getSize()) instead of Function.identity().

    – MC Emperor
    22 hours ago





















3














Solution-1



Just merging the two steps into one:



List<DistGroup> distGroups = bumperCars.stream()
.collect(Collectors.groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())))
.entrySet().stream()
.map(t -> {
DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
d.addCarCodes(t.getValue().stream().map(BumperCar::getCarCode).collect(Collectors.toList()));
return d;
})
.collect(Collectors.toList());


Solution-2



Your intermediate variable would be much better if you could use groupingBy twice using both the attributes and map the values as List of codes, something like:



Map<Integer, Map<String, List<String>>> sizeGroupedData = bumperCars.stream()
.collect(Collectors.groupingBy(BumperCar::getSize,
Collectors.groupingBy(BumperCar::getColor,
Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))));


and simply use forEach to add to the final list as:



List<DistGroup> distGroups = new ArrayList<>();
sizeGroupedData.forEach((size, colorGrouped) ->
colorGrouped.forEach((color, carCodes) -> distGroups.add(new DistGroup(size, color, carCodes))));


Note: I've updated your constructor such that it accepts the card codes list.



DistGroup(int size, String color, List<String> carCodes) {
this.size = size;
this.color = color;
addCarCodes(carCodes);
}


Further combining the second solution into one complete statement(though I would myself favor the forEach honestly):



List<DistGroup> distGroups = bumperCars.stream()
.collect(Collectors.groupingBy(BumperCar::getSize,
Collectors.groupingBy(BumperCar::getColor,
Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))))
.entrySet()
.stream()
.flatMap(a -> a.getValue().entrySet()
.stream().map(b -> new DistGroup(a.getKey(), b.getKey(), b.getValue())))
.collect(Collectors.toList());





share|improve this answer

































    0














    You can collect by by using BiConsumer that take (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) as parameters



    Collection<DistGroup> values = bumperCars.stream()
    .collect(HashMap::new, (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) -> {
    SizeColorCombination dg = new SizeColorCombination(bc.color, bc.size);
    DistGroup distGroup = res.get(dg);
    if(distGroup != null) {
    distGroup.addCarCode(bc.carCode);
    }else {
    List<String> codes = new ArrayList();
    distGroup = new DistGroup(bc.size, bc.color, codes);
    res.put(dg, distGroup);
    }
    },HashMap::putAll).values();





    share|improve this answer































      0














      Check out my library AbacusUtil:



      StreamEx.of(bumperCars)
      .groupBy(c -> Tuple.of(c.getSize(), c.getColor()), BumperCar::getCarCode)
      .toList(e -> new DistGroup(e.getKey()._1, e.getKey()._2, e.getValue());





      share|improve this answer























        Your Answer






        StackExchange.ifUsing("editor", function () {
        StackExchange.using("externalEditor", function () {
        StackExchange.using("snippets", function () {
        StackExchange.snippets.init();
        });
        });
        }, "code-snippets");

        StackExchange.ready(function() {
        var channelOptions = {
        tags: "".split(" "),
        id: "1"
        };
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function() {
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled) {
        StackExchange.using("snippets", function() {
        createEditor();
        });
        }
        else {
        createEditor();
        }
        });

        function createEditor() {
        StackExchange.prepareEditor({
        heartbeatType: 'answer',
        autoActivateHeartbeat: false,
        convertImagesToLinks: true,
        noModals: true,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: 10,
        bindNavPrevention: true,
        postfix: "",
        imageUploader: {
        brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
        contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
        allowUrls: true
        },
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        });


        }
        });














        draft saved

        draft discarded


















        StackExchange.ready(
        function () {
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54254064%2fhow-to-groupby-object-properties-and-map-to-another-object-using-java-8-streams%23new-answer', 'question_page');
        }
        );

        Post as a guest















        Required, but never shown

























        4 Answers
        4






        active

        oldest

        votes








        4 Answers
        4






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes









        4














        If we assume that DistGroup has hashCode/equals based on size and color, you could do it like this:



        bumperCars
        .stream()
        .map(x -> {
        List<String> list = new ArrayList<>();
        list.add(x.getCarCode());
        return new SimpleEntry<>(x, list);
        })
        .map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))
        .collect(Collectors.toMap(
        Function.identity(),
        Function.identity(),
        (left, right) -> {
        left.getCarCodes().addAll(right.getCarCodes());
        return left;
        }))
        .values(); // Collection<DistGroup>





        share|improve this answer


























        • Thanks, this code works for me. Note that I, after using this code, have tweaked the code a little, so the bumper cars are directly mapped to a DistGroup using .map(t -> new DistGroup(t.getSize(), t.getColor(), new ArrayList<>(Arrays.asList(t.getCarCode())))). Then I collect it with toMap with the same arguments as your code, but with the first argument being t -> new SimpleEntry<>(t.getColor(), t.getSize()) instead of Function.identity().

          – MC Emperor
          22 hours ago


















        4














        If we assume that DistGroup has hashCode/equals based on size and color, you could do it like this:



        bumperCars
        .stream()
        .map(x -> {
        List<String> list = new ArrayList<>();
        list.add(x.getCarCode());
        return new SimpleEntry<>(x, list);
        })
        .map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))
        .collect(Collectors.toMap(
        Function.identity(),
        Function.identity(),
        (left, right) -> {
        left.getCarCodes().addAll(right.getCarCodes());
        return left;
        }))
        .values(); // Collection<DistGroup>





        share|improve this answer


























        • Thanks, this code works for me. Note that I, after using this code, have tweaked the code a little, so the bumper cars are directly mapped to a DistGroup using .map(t -> new DistGroup(t.getSize(), t.getColor(), new ArrayList<>(Arrays.asList(t.getCarCode())))). Then I collect it with toMap with the same arguments as your code, but with the first argument being t -> new SimpleEntry<>(t.getColor(), t.getSize()) instead of Function.identity().

          – MC Emperor
          22 hours ago
















        4












        4








        4







        If we assume that DistGroup has hashCode/equals based on size and color, you could do it like this:



        bumperCars
        .stream()
        .map(x -> {
        List<String> list = new ArrayList<>();
        list.add(x.getCarCode());
        return new SimpleEntry<>(x, list);
        })
        .map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))
        .collect(Collectors.toMap(
        Function.identity(),
        Function.identity(),
        (left, right) -> {
        left.getCarCodes().addAll(right.getCarCodes());
        return left;
        }))
        .values(); // Collection<DistGroup>





        share|improve this answer















        If we assume that DistGroup has hashCode/equals based on size and color, you could do it like this:



        bumperCars
        .stream()
        .map(x -> {
        List<String> list = new ArrayList<>();
        list.add(x.getCarCode());
        return new SimpleEntry<>(x, list);
        })
        .map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))
        .collect(Collectors.toMap(
        Function.identity(),
        Function.identity(),
        (left, right) -> {
        left.getCarCodes().addAll(right.getCarCodes());
        return left;
        }))
        .values(); // Collection<DistGroup>






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Jan 18 at 13:04









        nullpointer

        46.3k1198190




        46.3k1198190










        answered Jan 18 at 12:56









        EugeneEugene

        69.4k999164




        69.4k999164













        • Thanks, this code works for me. Note that I, after using this code, have tweaked the code a little, so the bumper cars are directly mapped to a DistGroup using .map(t -> new DistGroup(t.getSize(), t.getColor(), new ArrayList<>(Arrays.asList(t.getCarCode())))). Then I collect it with toMap with the same arguments as your code, but with the first argument being t -> new SimpleEntry<>(t.getColor(), t.getSize()) instead of Function.identity().

          – MC Emperor
          22 hours ago





















        • Thanks, this code works for me. Note that I, after using this code, have tweaked the code a little, so the bumper cars are directly mapped to a DistGroup using .map(t -> new DistGroup(t.getSize(), t.getColor(), new ArrayList<>(Arrays.asList(t.getCarCode())))). Then I collect it with toMap with the same arguments as your code, but with the first argument being t -> new SimpleEntry<>(t.getColor(), t.getSize()) instead of Function.identity().

          – MC Emperor
          22 hours ago



















        Thanks, this code works for me. Note that I, after using this code, have tweaked the code a little, so the bumper cars are directly mapped to a DistGroup using .map(t -> new DistGroup(t.getSize(), t.getColor(), new ArrayList<>(Arrays.asList(t.getCarCode())))). Then I collect it with toMap with the same arguments as your code, but with the first argument being t -> new SimpleEntry<>(t.getColor(), t.getSize()) instead of Function.identity().

        – MC Emperor
        22 hours ago







        Thanks, this code works for me. Note that I, after using this code, have tweaked the code a little, so the bumper cars are directly mapped to a DistGroup using .map(t -> new DistGroup(t.getSize(), t.getColor(), new ArrayList<>(Arrays.asList(t.getCarCode())))). Then I collect it with toMap with the same arguments as your code, but with the first argument being t -> new SimpleEntry<>(t.getColor(), t.getSize()) instead of Function.identity().

        – MC Emperor
        22 hours ago















        3














        Solution-1



        Just merging the two steps into one:



        List<DistGroup> distGroups = bumperCars.stream()
        .collect(Collectors.groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())))
        .entrySet().stream()
        .map(t -> {
        DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
        d.addCarCodes(t.getValue().stream().map(BumperCar::getCarCode).collect(Collectors.toList()));
        return d;
        })
        .collect(Collectors.toList());


        Solution-2



        Your intermediate variable would be much better if you could use groupingBy twice using both the attributes and map the values as List of codes, something like:



        Map<Integer, Map<String, List<String>>> sizeGroupedData = bumperCars.stream()
        .collect(Collectors.groupingBy(BumperCar::getSize,
        Collectors.groupingBy(BumperCar::getColor,
        Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))));


        and simply use forEach to add to the final list as:



        List<DistGroup> distGroups = new ArrayList<>();
        sizeGroupedData.forEach((size, colorGrouped) ->
        colorGrouped.forEach((color, carCodes) -> distGroups.add(new DistGroup(size, color, carCodes))));


        Note: I've updated your constructor such that it accepts the card codes list.



        DistGroup(int size, String color, List<String> carCodes) {
        this.size = size;
        this.color = color;
        addCarCodes(carCodes);
        }


        Further combining the second solution into one complete statement(though I would myself favor the forEach honestly):



        List<DistGroup> distGroups = bumperCars.stream()
        .collect(Collectors.groupingBy(BumperCar::getSize,
        Collectors.groupingBy(BumperCar::getColor,
        Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))))
        .entrySet()
        .stream()
        .flatMap(a -> a.getValue().entrySet()
        .stream().map(b -> new DistGroup(a.getKey(), b.getKey(), b.getValue())))
        .collect(Collectors.toList());





        share|improve this answer






























          3














          Solution-1



          Just merging the two steps into one:



          List<DistGroup> distGroups = bumperCars.stream()
          .collect(Collectors.groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())))
          .entrySet().stream()
          .map(t -> {
          DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
          d.addCarCodes(t.getValue().stream().map(BumperCar::getCarCode).collect(Collectors.toList()));
          return d;
          })
          .collect(Collectors.toList());


          Solution-2



          Your intermediate variable would be much better if you could use groupingBy twice using both the attributes and map the values as List of codes, something like:



          Map<Integer, Map<String, List<String>>> sizeGroupedData = bumperCars.stream()
          .collect(Collectors.groupingBy(BumperCar::getSize,
          Collectors.groupingBy(BumperCar::getColor,
          Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))));


          and simply use forEach to add to the final list as:



          List<DistGroup> distGroups = new ArrayList<>();
          sizeGroupedData.forEach((size, colorGrouped) ->
          colorGrouped.forEach((color, carCodes) -> distGroups.add(new DistGroup(size, color, carCodes))));


          Note: I've updated your constructor such that it accepts the card codes list.



          DistGroup(int size, String color, List<String> carCodes) {
          this.size = size;
          this.color = color;
          addCarCodes(carCodes);
          }


          Further combining the second solution into one complete statement(though I would myself favor the forEach honestly):



          List<DistGroup> distGroups = bumperCars.stream()
          .collect(Collectors.groupingBy(BumperCar::getSize,
          Collectors.groupingBy(BumperCar::getColor,
          Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))))
          .entrySet()
          .stream()
          .flatMap(a -> a.getValue().entrySet()
          .stream().map(b -> new DistGroup(a.getKey(), b.getKey(), b.getValue())))
          .collect(Collectors.toList());





          share|improve this answer




























            3












            3








            3







            Solution-1



            Just merging the two steps into one:



            List<DistGroup> distGroups = bumperCars.stream()
            .collect(Collectors.groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())))
            .entrySet().stream()
            .map(t -> {
            DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
            d.addCarCodes(t.getValue().stream().map(BumperCar::getCarCode).collect(Collectors.toList()));
            return d;
            })
            .collect(Collectors.toList());


            Solution-2



            Your intermediate variable would be much better if you could use groupingBy twice using both the attributes and map the values as List of codes, something like:



            Map<Integer, Map<String, List<String>>> sizeGroupedData = bumperCars.stream()
            .collect(Collectors.groupingBy(BumperCar::getSize,
            Collectors.groupingBy(BumperCar::getColor,
            Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))));


            and simply use forEach to add to the final list as:



            List<DistGroup> distGroups = new ArrayList<>();
            sizeGroupedData.forEach((size, colorGrouped) ->
            colorGrouped.forEach((color, carCodes) -> distGroups.add(new DistGroup(size, color, carCodes))));


            Note: I've updated your constructor such that it accepts the card codes list.



            DistGroup(int size, String color, List<String> carCodes) {
            this.size = size;
            this.color = color;
            addCarCodes(carCodes);
            }


            Further combining the second solution into one complete statement(though I would myself favor the forEach honestly):



            List<DistGroup> distGroups = bumperCars.stream()
            .collect(Collectors.groupingBy(BumperCar::getSize,
            Collectors.groupingBy(BumperCar::getColor,
            Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))))
            .entrySet()
            .stream()
            .flatMap(a -> a.getValue().entrySet()
            .stream().map(b -> new DistGroup(a.getKey(), b.getKey(), b.getValue())))
            .collect(Collectors.toList());





            share|improve this answer















            Solution-1



            Just merging the two steps into one:



            List<DistGroup> distGroups = bumperCars.stream()
            .collect(Collectors.groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())))
            .entrySet().stream()
            .map(t -> {
            DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());
            d.addCarCodes(t.getValue().stream().map(BumperCar::getCarCode).collect(Collectors.toList()));
            return d;
            })
            .collect(Collectors.toList());


            Solution-2



            Your intermediate variable would be much better if you could use groupingBy twice using both the attributes and map the values as List of codes, something like:



            Map<Integer, Map<String, List<String>>> sizeGroupedData = bumperCars.stream()
            .collect(Collectors.groupingBy(BumperCar::getSize,
            Collectors.groupingBy(BumperCar::getColor,
            Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))));


            and simply use forEach to add to the final list as:



            List<DistGroup> distGroups = new ArrayList<>();
            sizeGroupedData.forEach((size, colorGrouped) ->
            colorGrouped.forEach((color, carCodes) -> distGroups.add(new DistGroup(size, color, carCodes))));


            Note: I've updated your constructor such that it accepts the card codes list.



            DistGroup(int size, String color, List<String> carCodes) {
            this.size = size;
            this.color = color;
            addCarCodes(carCodes);
            }


            Further combining the second solution into one complete statement(though I would myself favor the forEach honestly):



            List<DistGroup> distGroups = bumperCars.stream()
            .collect(Collectors.groupingBy(BumperCar::getSize,
            Collectors.groupingBy(BumperCar::getColor,
            Collectors.mapping(BumperCar::getCarCode, Collectors.toList()))))
            .entrySet()
            .stream()
            .flatMap(a -> a.getValue().entrySet()
            .stream().map(b -> new DistGroup(a.getKey(), b.getKey(), b.getValue())))
            .collect(Collectors.toList());






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Jan 18 at 13:18

























            answered Jan 18 at 12:38









            nullpointernullpointer

            46.3k1198190




            46.3k1198190























                0














                You can collect by by using BiConsumer that take (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) as parameters



                Collection<DistGroup> values = bumperCars.stream()
                .collect(HashMap::new, (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) -> {
                SizeColorCombination dg = new SizeColorCombination(bc.color, bc.size);
                DistGroup distGroup = res.get(dg);
                if(distGroup != null) {
                distGroup.addCarCode(bc.carCode);
                }else {
                List<String> codes = new ArrayList();
                distGroup = new DistGroup(bc.size, bc.color, codes);
                res.put(dg, distGroup);
                }
                },HashMap::putAll).values();





                share|improve this answer




























                  0














                  You can collect by by using BiConsumer that take (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) as parameters



                  Collection<DistGroup> values = bumperCars.stream()
                  .collect(HashMap::new, (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) -> {
                  SizeColorCombination dg = new SizeColorCombination(bc.color, bc.size);
                  DistGroup distGroup = res.get(dg);
                  if(distGroup != null) {
                  distGroup.addCarCode(bc.carCode);
                  }else {
                  List<String> codes = new ArrayList();
                  distGroup = new DistGroup(bc.size, bc.color, codes);
                  res.put(dg, distGroup);
                  }
                  },HashMap::putAll).values();





                  share|improve this answer


























                    0












                    0








                    0







                    You can collect by by using BiConsumer that take (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) as parameters



                    Collection<DistGroup> values = bumperCars.stream()
                    .collect(HashMap::new, (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) -> {
                    SizeColorCombination dg = new SizeColorCombination(bc.color, bc.size);
                    DistGroup distGroup = res.get(dg);
                    if(distGroup != null) {
                    distGroup.addCarCode(bc.carCode);
                    }else {
                    List<String> codes = new ArrayList();
                    distGroup = new DistGroup(bc.size, bc.color, codes);
                    res.put(dg, distGroup);
                    }
                    },HashMap::putAll).values();





                    share|improve this answer













                    You can collect by by using BiConsumer that take (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) as parameters



                    Collection<DistGroup> values = bumperCars.stream()
                    .collect(HashMap::new, (HashMap<SizeColorCombination, DistGroup> res, BumperCar bc) -> {
                    SizeColorCombination dg = new SizeColorCombination(bc.color, bc.size);
                    DistGroup distGroup = res.get(dg);
                    if(distGroup != null) {
                    distGroup.addCarCode(bc.carCode);
                    }else {
                    List<String> codes = new ArrayList();
                    distGroup = new DistGroup(bc.size, bc.color, codes);
                    res.put(dg, distGroup);
                    }
                    },HashMap::putAll).values();






                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered Jan 18 at 15:00









                    SEY_91SEY_91

                    1,093615




                    1,093615























                        0














                        Check out my library AbacusUtil:



                        StreamEx.of(bumperCars)
                        .groupBy(c -> Tuple.of(c.getSize(), c.getColor()), BumperCar::getCarCode)
                        .toList(e -> new DistGroup(e.getKey()._1, e.getKey()._2, e.getValue());





                        share|improve this answer




























                          0














                          Check out my library AbacusUtil:



                          StreamEx.of(bumperCars)
                          .groupBy(c -> Tuple.of(c.getSize(), c.getColor()), BumperCar::getCarCode)
                          .toList(e -> new DistGroup(e.getKey()._1, e.getKey()._2, e.getValue());





                          share|improve this answer


























                            0












                            0








                            0







                            Check out my library AbacusUtil:



                            StreamEx.of(bumperCars)
                            .groupBy(c -> Tuple.of(c.getSize(), c.getColor()), BumperCar::getCarCode)
                            .toList(e -> new DistGroup(e.getKey()._1, e.getKey()._2, e.getValue());





                            share|improve this answer













                            Check out my library AbacusUtil:



                            StreamEx.of(bumperCars)
                            .groupBy(c -> Tuple.of(c.getSize(), c.getColor()), BumperCar::getCarCode)
                            .toList(e -> new DistGroup(e.getKey()._1, e.getKey()._2, e.getValue());






                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Jan 19 at 5:31









                            123-xyz123-xyz

                            60125




                            60125






























                                draft saved

                                draft discarded




















































                                Thanks for contributing an answer to Stack Overflow!


                                • Please be sure to answer the question. Provide details and share your research!

                                But avoid



                                • Asking for help, clarification, or responding to other answers.

                                • Making statements based on opinion; back them up with references or personal experience.


                                To learn more, see our tips on writing great answers.




                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function () {
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54254064%2fhow-to-groupby-object-properties-and-map-to-another-object-using-java-8-streams%23new-answer', 'question_page');
                                }
                                );

                                Post as a guest















                                Required, but never shown





















































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown

































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown







                                Popular posts from this blog

                                Liquibase includeAll doesn't find base path

                                How to use setInterval in EJS file?

                                Petrus Granier-Deferre