2016-11-10 2 views
0

Я тренируюсь по использованию Java Steam API и реализовал то, что упоминается в заголовке.
Но я недоволен своим кодом.Объединение объектов сущностей по внешнему ключу с API Java Stream API

Например, существуют три класса сущностей, которые являются простыми неизменяемыми компонентами.

КОД:

public class Country { 
    private final Integer countryId; // PK, primary key 
    private final String name; 

    public Country(Integer countryId, String name) { 
     this.countryId = countryId; 
     this.name = name; 
    } 
    // omitting getters 
} 

public class State { 
    private final Integer stateId; // PK 
    private final Integer countryId; // FK, foreign key 
    private final String name; 

    public State(Integer stateId, Integer countryId, String name) { 
     this.stateId = stateId; 
     this.countryId = countryId; 
     this.name = name; 
    } 
    // omitting getters 
} 

public class City { 
    private final Integer cityId; // PK 
    private final Integer stateId; // FK 
    private final String name; 

    public City(Integer cityId, Integer stateId, String name) { 
     this.cityId = cityId; 
     this.stateId = stateId; 
     this.name = name; 
    } 
    // omitting getters 
} 

Эти сущности отношение один ко многим, не многие ко многим.

Я хочу создать Map<Country, Map<State, City>> объект из коллекции сущностей, как Collection<Country>, Collection<State> и Collection<City> с использованием ПК и отношения FK.

Моя реализация здесь.

КОД:

// entity collections 
Set<Country> countries = Collections.singleton(new Country(1, "America")); 
Set<State> states = Collections.singleton(new State(30, 1, "Wasington")); 
Set<City> cities = Collections.singleton(new City(500, 30, "Wasington, D.C.")); 

// intermediate maps 
Map<Integer, City> fkCityMap = cities.stream() 
    .collect(Collectors.toMap(City::getStateId, Function.identity())); 
Map<Integer, State> fkStateMap = states.stream() 
    .collect(Collectors.toMap(State::getCountryId, Function.identity())); 
Map<Integer, Map<State, City>> fkStateCityMap = fkStateMap.entrySet().stream() 
    .collect(Collectors.toMap(Entry::getKey, entry -> Collections.singletonMap(
     entry.getValue(), fkCityMap.get(entry.getValue().getStateId())))); 

// result 
Map<Country, Map<State, City>> mapWhatIWant = countries.stream() 
    .collect(Collectors.toMap(Function.identity(), 
     country -> fkStateCityMap.get(country.getCountryId()))); 

Он работает, но не элегантно, особенно комментировал "промежуточные карты" часть, я думаю.
Есть ли лучший способ реализовать это?


UPDATE

Есть mistakes mentioned by Holger.

  1. типа, что я хочу Map<Country, Map<State, Collection<City>>>,
    не Map<Country, Map<State, City>>.

  2. я понял о Вашингтоне и Вашингтоне, округ Колумбия
    Таким образом, код комментировал коллекции сущностей является плохим примером.

+1

Во-первых, вы должны переосмыслить свою фактическую задачу. Действительно ли существует сопоставление 1: 1 между «Государством» и «Сити»? – Holger

+0

@ Хольгер Прошу прощения, я не английский динамик. Это неправильное имя. Я обновлю это позже. Спасибо за ваш комментарий. –

+0

@Holger Сопоставление между 'State' и' City' не 1: 1. Это одно для многих **. Итак, введите то, что я хочу, это «Карта <Страна, Карта <Штат, Коллекция >>' как упоминалось ваш комментарий. –

ответ

1

Путь, вы определили свои промежуточные карты, т.е. Map<Integer, City> fkCityMap, отображение из дых в город, и Map<Integer, State> fkStateMap, отображение из ID страны в состояние, вы создаете предположение, что может быть только один State в Country и ровно один City в State, что косвенно также позволяет только один City в целом Country.

Конечно, вы не заметите, пока ваши тестовые данные состоят только из одного Country, одного State и одного City. Хуже того, даже ваш предполагаемый результат типа Map<Country, Map<State, City>> работает только в том случае, если имеется только один City за State. Таким образом, не только ваша реализация нарушена, но и определение задачи.

Давайте переопределить тип результата, как Map<Country, Map<State, Set<City>>>, чтобы позволить более одного City за State, так что вы можете осуществить операцию

Map<Country, Map<State, Set<City>>> mapThatYouWant = countries.stream()     
    .collect(Collectors.toMap(Function.identity(), c->states.stream() 
     .filter(s -> Objects.equals(s.getCountryId(), c.getCountryId())) 
     .collect(Collectors.toMap(Function.identity(), s->cities.stream() 
      .filter(city -> Objects.equals(city.getStateId(), s.getStateId())) 
      .collect(Collectors.toSet()))))); 

, но обратите внимание, что создание промежуточных карт для поиска предметов может быть на самом деле более эффективной чем поиск множеств линейно многократно. Вам нужно только заботиться о создании правильного вида карт.

Map<Integer,Country> countryById = countries.stream() 
    .collect(Collectors.toMap(Country::getCountryId, Function.identity())); 
Map<Integer,Set<City>> citiesByStateId = cities.stream() 
    .collect(Collectors.groupingBy(City::getStateId, Collectors.toSet())); 

Map<Country, Map<State, Set<City>>> mapThatYouWant = states.stream() 
    .collect(Collectors.groupingBy(s -> countryById.get(s.getCountryId()), 
     Collectors.toMap(Function.identity(), 
      s -> citiesByStateId.getOrDefault(s.getStateId(), Collections.emptySet())))); 

Кстати, Washington, D.C. не в state Washington.

+0

Спасибо за ваш комментарий. Я понимаю, что я принял. Два кода, которые вы показали, легко читаются и очищают то, что этот код хочет сделать. Это хороший пример использования 'Optional' и' groupingBy'. Я буду изучать Java Stream API все больше и больше с вашим комментарием. Большое спасибо! –