将非规范化扁平化数据转为层级Java对象能否仅用Java8+Collectors单步实现
非规范化扁平化数据转层级Java对象实现方案
以下是将扁平化的城市三元组数据转换为国家-省份-城市层级对象的实现方案,包含输入输出示例、原有实现代码,以及针对单步实现问题的解答:
输入示例
[ { "countryName" : "USA", "stateName" : "FL", "cityName" : "Miami" }, { "countryName" : "USA", "stateName" : "FL", "cityName" : "Doral" }, { "countryName" : "India", "stateName" : "Tamilnadu", "cityName" : "Chennai" }, { "countryName" : "India", "stateName" : "Karnataka", "cityName" : "Bangalore" } ]
输出示例
[ { "countryName" : "USA", "states" : [ { "stateName" : "FL", "cities" : [ { "cityName" : "Miami" }, { "cityName" : "Doral" } ] } ] }, { "countryName" : "India", "states" : [ { "stateName" : "Karnataka", "cities" : [ { "cityName" : "Bangalore" } ] }, { "stateName" : "Tamilnadu", "cities" : [ { "cityName" : "Chennai" } ] } ] } ]
原有实现代码
import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toSet; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashSet; import java.util.List; import java.util.Set; import lombok.Data; public class Normalizer { public static void main(String[] args) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); final var objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); Tuple t1 = Tuple.of("USA", "FL", "Miami"); Tuple t2 = Tuple.of("USA", "FL", "Doral"); Tuple t3 = Tuple.of("India", "Tamilnadu", "Chennai"); Tuple t4 = Tuple.of("India", "Karnataka", "Bangalore"); List<Tuple> tuples = List.of(t1, t2, t3, t4); System.out.println("INPUT:\n" + objectWriter.writeValueAsString(tuples)); System.out.println("---------------------------------------------"); final var countries = normalize(tuples); System.out.println("OUTPUT:\n" + objectWriter.writeValueAsString(countries)); } public static Set<Country> normalize(List<Tuple> tuples) { System.out.println("Step 1: Grouping"); final var map = tuples.stream() .collect(groupingBy(Country::of, groupingBy(State::of, mapping(City::of, toSet())))); System.out.println("Step 2: Creating Object Hierarchy"); Set<Country> countries = new HashSet<>(); map.forEach((country, value) -> { countries.add(country); Set<State> states = new HashSet<>(); value.forEach((state, value1) -> { state.setCities(value1); states.add(state); }); country.setStates(states); }); return countries; } @Data(staticConstructor = "of") public static class Tuple { private String countryName; private String stateName; private String cityName; public static Tuple of(String countryName, String stateName, String cityName) { Tuple t1 = new Tuple(); t1.setCountryName(countryName); t1.setStateName(stateName); t1.setCityName(cityName); return t1; } } @Data(staticConstructor = "of") public static class Country { private String countryName; private Set<State> states = new HashSet<>(); public static Country of(Tuple tuple) { Country country = new Country(); country.setCountryName(tuple.getCountryName()); return country; } } @Data(staticConstructor = "of") public static class State { private String stateName; private Set<City> cities = new HashSet<>(); public static State of(Tuple tuple) { State state = new State(); state.setStateName(tuple.getStateName()); return state; } } @Data(staticConstructor = "of") public static class City { private String cityName; public static City of(Tuple tuple) { City city = new City(); city.setCityName(tuple.getCityName()); return city; } } }
问题解答:单步Collectors实现方案
可以实现仅通过Java 8+的Collectors相关方法完成整个转换,不需要拆分两步执行。核心是使用collectingAndThen收集器,将分组后的结果直接做层级对象的属性赋值,整个逻辑完全封装在Stream的collect操作内。
优化后的单步normalize方法代码如下:
import static java.util.stream.Collectors.*; public static Set<Country> normalize(List<Tuple> tuples) { return tuples.stream() .collect(collectingAndThen( // 原有分组逻辑不变,得到<国家, <省份, 城市集合>>的嵌套Map groupingBy(Country::of, groupingBy(State::of, mapping(City::of, toSet()))), // 对分组结果直接处理,赋值层级属性后返回国家集合 countryToStateMap -> { countryToStateMap.forEach((country, stateToCityMap) -> { // 给每个省份赋值对应的城市集合 stateToCityMap.forEach(State::setCities); // 给国家赋值对应的省份集合 country.setStates(stateToCityMap.keySet()); }); return countryToStateMap.keySet(); } )); }
实现说明
collectingAndThen是Java 8自带的收集器,作用是接收两个参数:上游收集器、结果转换函数,刚好适合把分组得到的Map直接转换为我们需要的对象集合- 因为用到了Lombok的
@Data注解,Country和State的equals/hashCode仅基于名称字段生成,所以分组得到的Key不会重复,直接取Key集合即可得到正确的层级对象,不需要额外去重 - 整个实现没有中间变量暴露,所有逻辑都在collect操作内完成,符合单步实现的要求
内容的提问来源于stack exchange,提问作者Arun Chandrasekaran
相关产品推荐
相关产品推荐

