简体   繁体   中英

How to map multiple objects having two identical values and one different value to one object with list of those different values?

I have an object called RawCarEnumData having 3 enums:

public class RawCarEnumData {

    private BrandEnum brand;
    private ModelEnum model;
    private ColorEnum color;

    //getters, setters, constructors

}

and an object CarEnumData having 2 enums and a list of enums:

public class CarEnumData {

    private BrandEnum brand;
    private ModelEnum model;
    private List<ColorEnum> colors;

    //getters, setters, constructors

}

Now I have a list of RawCarEnumData objects:

List<RawCarEnumData> rawCarEnumDataList = List.of(
    new RawCarEnumData(BrandEnum.FORD, ModelEnum.FIESTA, ColorEnum.RED),
    new RawCarEnumData(BrandEnum.FORD, ModelEnum.FIESTA, ColorEnum.BLUE),
    new RawCarEnumData(BrandEnum.FORD, ModelEnum.FIESTA, ColorEnum.YELLOW),
    new RawCarEnumData(BrandEnum.FORD, ModelEnum.FOCUS, ColorEnum.YELLOW)
}

Now I want to use stream API to map the List<RawCarEnumData> to List<CarEnumData> so the final output will look like this:

List<CarEnumData> carEnumData:
    - CarEnumData(BrandEnum.FORD, ModelEnum.FIESTA, List.of(ColorEnum.RED, ColorEnum.BLUE, ColorEnum.YELLOW))
    - CarEnumData(BrandEnum.FORD, ModelEnum.FOCUS, List.of(ColorEnum.YELLOW))

I have tried a few things but without success. How can this be achieved? Is this even possible?

I know this is a Java question but I like to provide a Kotlin answer. It might help you come up with a Java solution or you might even want to include the Kotlin solution in your project since it should be interoperable with Java. The following code seems to do what you would like:

fun main() {
    val rawCarEnumDataList = listOf(
        RawCarEnumData(BrandEnum.FORD, ModelEnum.FIESTA, ColorEnum.RED),
        RawCarEnumData(BrandEnum.FORD, ModelEnum.FIESTA, ColorEnum.BLUE),
        RawCarEnumData(BrandEnum.FORD, ModelEnum.FIESTA, ColorEnum.YELLOW),
        RawCarEnumData(BrandEnum.FORD, ModelEnum.FOCUS, ColorEnum.YELLOW)
    )
    
    val carEnumData = rawCarEnumDataList
        .groupBy { Pair(it.brand, it.model) }
        .values.map { cars -> CarEnumData(cars.first().brand,cars.first().model, cars.map { it.color }.distinct()) }

    print(carEnumData)
    // prints [CarEnumData(brand=FORD, model=FIESTA, colors=[RED, BLUE, YELLOW]), CarEnumData(brand=FORD, model=FOCUS, colors=[YELLOW])]
}

data class RawCarEnumData(
    val brand: BrandEnum,
    val model: ModelEnum,
    val color: ColorEnum
)

data class CarEnumData (
    val brand: BrandEnum,
    val model: ModelEnum,
    val colors: List<ColorEnum>
)

enum class BrandEnum { FORD }
enum class ModelEnum { FIESTA, FOCUS }
enum class ColorEnum { RED, BLUE, YELLOW }

So what you want to do is to group your RawCarEnumData s by brand and model, and then reduce each list of RawCarEnumData s into one CarEnumData . One way to do this would be the following:

final var result =
    rawCarEnumDataList.stream()
        .collect(
            Collectors.groupingBy(
                car -> Map.entry(car.brand, car.model),
                Collectors.mapping(
                    car -> new CarEnumData(car.brand(), car.model(), List.of(car.color)),
                    Collectors.collectingAndThen(
                        Collectors.reducing(
                            (car1, car2) ->
                                new CarEnumData(
                                    car1.brand(),
                                    car2.model(),
                                    Stream.concat(car1.colors.stream(), car2.colors.stream())
                                        .toList())),
                        Optional::orElseThrow))))
        .values();

Breaking it down, the Collectors.groupingBy gives you a map with keys given by applying a function, and a list of all values that corresponds to that key. The second argument to Collectors.groupingBy is a downstream collector of that list. Here mapping maps each value to a CarEnumData with just a single color. Collectors.mappingBy also takes a downstream collector as a second argument, where I used Collectors.reducing to merge all CarEnumData s with the same brand and model into one, by merging the lists of colors with Stream.concat(car1.colors.stream(), car2.colors.stream()).toList() . The reducing collector returns an optional which is empty if there are no values in the stream, but in this case groupingBy guarantees me at least one value in every group, so I can safely retrieve the value with Optional.orElseThrow() . Finally I can pick out the values() of the map which is the desired collection.

You didn't mention which Java version you use, if using an older version you may have to use collect(Collectors.toList) and some other way to create the group key.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM