按对象的属性将地图分组到新地图

问题描述

首先,让我添加实际的“示例代码”:

Map<CarBrand,List<Car>> allCarsAndBrands = new HashMap();

final String bmwBrandName = "BMW";
final String audiBrandName = "AUDI";

List<Car> bmwCars = new ArrayList();
bmwCars.add(new Car(CarType.FAST,"Z4","silver",bmwBrandName));
bmwCars.add(new Car(CarType.FAST,"M3","red",bmwBrandName));
bmwCars.add(new Car(CarType.SLOW,"X1","black",bmwBrandName));

List<Car> audiCars = new ArrayList();
audiCars.add(new Car(CarType.FAST,"S3","yellow",audiBrandName));
audiCars.add(new Car(CarType.FAST,"R8",audiBrandName));
audiCars.add(new Car(CarType.SLOW,"A1","white",audiBrandName));

allCarsAndBrands.put(new CarBrand(bmwBrandName),bmwCars);
allCarsAndBrands.put(new CarBrand(audiBrandName),audiCars);

Map<CarType,Map<CarBrand,List<Car>>> mappedCars;

问题

我的目标是用mappedCars填充CarType,这将导致两个大集合:一个包含所有FAST汽车,另一个包含所有SLOW汽车(或任何未来的“类型”,每个一个具有CarBrand及其相关汽车的先前地图结构。

我目前无法为此“在其他地图内包含列表的地图”找到合适的集合/流。我在其他情况下使用简单的地图/列表,但事实证明这对我来说比较棘手。

尝试

这是初始代码“尝试”:

mappedCars = allCarsAndBrands.entrySet()
                             .stream()
                             .collect(
                               groupingBy(Car::getType,groupingBy(Map.Entry::getKey)
                               )
                             );

我也收到“无法引用非静态错误”(Map.Entry :: getKey),但这是由于我未能匹配实际的预期收益(Static context cannot access non-static in Collectors

这时我很困惑,也尝试过使用Collectors.toMap,但仍然无法获得有效的分组。

其他

以下是此示例的类定义

class CarBrand {
   CarBrand(String name) {
      this.name = name;
   }
   String name;
}

class Car {
    Car(CarType type,String name,String color,String brandName) {
        this.type = type;
        this.name = name;
        this.color = color;
        this.brandName = brandName;
    }

    public CarType getType() {
        return type;
    }

    CarType type;
    String name;
    String color;
    String brandName;
}

enum CarType {
   FAST,SLOW,}

编辑:“脏”解决方

这是一个“骇人听闻的”解决方案(根据评论建议,将检查答案!):

Map<CarType,List<Car>>> mappedCars = allCarsAndBrands
                .values()
                .stream()
                .flatMap(List::stream)
                .collect(Collectors.groupingBy(
                        Car::getType,Collectors.groupingBy(
                                car -> allCarsAndBrands.keySet().stream().filter(brand -> brand.name == car.brandName).findFirst().get(),Collectors.toList()
                        )
                ));

评论中所述(应该在此之前添加),存在“业务约束”,该约束为解决方增加了一些限制。我也不想创建一个新的CarBrand,因为在现实世界中,这并不像上面看到的那么简单...但是,再次使用原始地图和filter + find实在是不好。

解决方法

如评论中所讨论的,如果将Make作为Car类的字段包含在内,这很容易做到。

根据您的最新评论,最简单的方法是使用混合解决方案,该解决方案使用Stream和Java 8中引入的Map接口的其他功能。

数据

Map<CarBrand,List<Car>> allCarsAndBrands = new HashMap<>();

final String bmwBrandName = "BMW";
final String audiBrandName = "AUDI";

List<Car> bmwCars = new ArrayList<>();
bmwCars.add(new Car(CarType.FAST,"Z4","silver"));
bmwCars.add(new Car(CarType.FAST,"M3","red"));
bmwCars.add(new Car(CarType.SLOW,"X1","black"));

List<Car> audiCars = new ArrayList<>();
audiCars.add(new Car(CarType.FAST,"S3","yellow"));
audiCars.add(new Car(CarType.FAST,"R8","silver"));
audiCars.add(new Car(CarType.SLOW,"A1","white"));

allCarsAndBrands.put(new CarBrand(bmwBrandName),bmwCars);
allCarsAndBrands.put(new CarBrand(audiBrandName),audiCars);

过程

这是通过为每个Map<CarType,List<Car>>创建一个CarBrand然后反转键来实现的。您可能不熟悉的唯一新功能是computeIfAbsent

Map<CarType,Map<CarBrand,List<Car>>> map = new HashMap<>();

allCarsAndBrands.forEach((brand,carList) -> {
    Map<CarType,List<Car>> typeMap = carList.stream()
            .collect(Collectors.groupingBy(Car::getType));
    typeMap.forEach((type,lst) -> {
        map.computeIfAbsent(type,value->
                new HashMap<CarBrand,List<Car>>())
                    .computeIfAbsent(brand,value->new ArrayList<>())
                    .addAll(lst);
        }
    );
});

打印结果

map.forEach((carType,brandMap) -> {
    System.out.println(carType);
    brandMap.forEach((brand,carList) -> {
        System.out.println("     " + brand + " -> " + carList);
    });
});

打印

FAST
     AUDI -> [{FAST,S3,yellow},{FAST,R8,silver}]
     BMW -> [{FAST,Z4,silver},M3,red}]
SLOW
     AUDI -> [{SLOW,A1,white}]
     BMW -> [{SLOW,X1,black}]

注意:{}之间的值是toString类的Car替代。

,

使用现有模型,以及嵌套分组的初始方法,您都在朝正确的方向思考。可以考虑在迭代条目时将Map的值部分展平。

allCarsAndBrands.entrySet().stream()
        .flatMap(e -> e.getValue().stream()
                .map(car -> new AbstractMap.SimpleEntry<>(e.getKey(),car)))

一旦有了,分组概念的工作原理几乎相同,但是现在默认返回的分组值将是条目类型。因此,进一步需要mapping。这使整体解决方案类似于:

Map<CarType,List<Car>>> mappedCars =
        allCarsAndBrands.entrySet().stream()
                .flatMap(e -> e.getValue().stream()
                        .map(car -> new AbstractMap.SimpleEntry<>(e.getKey(),car)))
                .collect(Collectors.groupingBy(e -> e.getValue().getType(),Collectors.groupingBy(Map.Entry::getKey,Collectors.mapping(Map.Entry::getValue,Collectors.toList()))));

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...