问题描述
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()))));