Map<Foo1, Map<Foo2, BigDecimal>> zamiast Map<Stream<Stream<Foo1>>, Map<Foo2, BigDecimal>> (pozbycie się Stream w kluczu mapy)

0

Staram się stworzyć mapę

Map<AccountType, Map<User, BigDecimal>>

jednak udało mi się stworzyć mapę z kluczem obudowanym w Streamy.

Map<Stream<Stream<AccountType>>, Map<User, BigDecimal>>

Chcę wyciągnąć mapę gdzie kluczem jest typ rachunku a wartością mapa mężczyzn posiadających ten rachunek, gdzie kluczem jest obiekt User a wartością suma pieniędzy. Oto jak to zrobiłem:

    Map<Stream<Stream<AccountType>>, Map<User, BigDecimal>> getMapWithAccountTypeKeyAndSumMoneyForManInPLN() {
        return getCompanyStream() // 1
                .collect(Collectors.toMap(
                        company -> company.getUsers() // 2
                                .stream()
                                .map(user -> user.getAccounts() // 3
                                        .stream()
                                        .map(Account::getType)), // 4
                        this::manWithSumMoneyOnAccounts // 5
                ));
    }
  1. pobieram stream firm
  2. z firmy wyciągam użytkowników
  3. z użytkowników wyciągam konta
  4. i z kont wyciągam typ, czyli teoretycznie mam tutaj jako klucz AccountType w mapie zewnętrznej
  5. przekazuję firmę do tej metody, ta metoda zwraca wartość dla mapy wewnętrznej Map<User, BigDecimal>
    private Map<User, BigDecimal> manWithSumMoneyOnAccounts(final Company company) { // 6
        return company
                .getUsers()  // 7
                .stream()
                .filter(user -> user.getSex().equals(Sex.MAN)) // 8
                .collect(Collectors.toMap(
                        Function.identity(), // 9
                        this::getSumUserAmountInPLN // 10
                ));
    }
  1. przekazana firma z metody powyżej
  2. pobieram użytkowników
  3. filtruję że biorę pod uwagę tylko mężczyzn
  4. tworzę mapę wewnętrzną, ta metoda zwraca mi obiekt User więc w teorii mam klucz User w mapie wewnętrznej
  5. wywołuję funkcję do zliczania przekazując obiekt konkretnego użytkownika, zwraca mi wartość dla mapy wewnętrznej
    private BigDecimal getSumUserAmountInPLN(final User user) {
        return user.getAccounts()
                .stream()
                .map(this::getAccountAmountInPLN)
                .reduce(BigDecimal.ZERO, BigDecimal::add); // 11
    }
  1. policzona kwota, która zwraca BigDecimal czyli jest to ta wartość do mapy wewnętrznej

Company zawiera List< User> users czyli firma zawiera listę użytkowników,
User zawiera List<Account> accounts czyli użytkownik zawiera listę kont,
Account zawiera AccountType type (AccountType to enum) i Account zawiera Currency currency (Currency to też enum) czyli konto zawiera typ konta i walutę, zawiera także kwotę rachunku w typie BigDecimal

Zacząłem jeszcze inaczej do tego podchodzić, mniej więcej w taki sposób:

    Map<AccountType, Map<User, BigDecimal>> getMapWithAccountTypeKeyAndSumMoneyForManInPLN() {
        Predicate<User> isMan = m -> m.getSex() == Sex.MAN;

        return getUserStream()
                .filter(isMan)
                .collect(Collectors.groupingBy(Account::getType),
                        Collectors.mapping(Function.identity(), this::getSumUserAmountInPLN));
    }

jednak tutaj nawet nie mogę skompilować bo mam np informację dla klucza w mapie zewnętrznej Non-static method cannot be referenced from a static context, a inaczej nie mogę się dobrać do typu konta, więc raczej to jest nie ta droga.

0

Tak na pierwszy rzut oka nie zaglądając w szczegóły spróbuj coś takiego

    Map<Stream<Stream<AccountType>>, Map<User, BigDecimal>> getMapWithAccountTypeKeyAndSumMoneyForManInPLN() {
        return getCompanyStream() // 1
                .collect(Collectors.toMap(
                        company -> company.getUsers() // 2
                                .stream()
                                .flatMap(user -> user.getAccounts().stream())
                                .map(Account::getType)), // 4
                        this::manWithSumMoneyOnAccounts // 5
                ));
    }

Tylko wtedy obawiam się, że może polecieć wyjątek typu mapa posiada już dany klucz.

1

Faktycznie, to chyba poszedłbym w coś takiego:

Map<AccountType, Map<User, BigDecimal>> collect = companyList.stream()
.flatMap(comp -> comp.getUsers().stream())
.flatMap(user -> user.getAccounts().stream())
.map(Account::getType)
.collect(Collectors.toMap(type -> type, type -> manWithSumMoneyOnAccountsByType(type, companyList)));

I w tej metodzie manWithSumMoneyOnAccountsByType robisz mape Map<User, BigDecimal> która jest generowana na podstawie accountType.

0
podroznik napisał(a):

Faktycznie, to chyba poszedłbym w coś takiego:

Map<AccountType, Map<User, BigDecimal>> collect = companyList.stream()
.flatMap(comp -> comp.getUsers().stream())
.flatMap(user -> user.getAccounts().stream())
.map(Account::getType)
.collect(Collectors.toMap(type -> type, type -> manWithSumMoneyOnAccountsByType(type, companyList)));

I w tej metodzie manWithSumMoneyOnAccountsByType robisz mape Map<User, BigDecimal> która jest generowana na podstawie accountType.

Dzięki ponownie. Metoda zwraca to co chciałem dzięki Twojemu kodowi. Zrobiłem u siebie w ten sposób:

    Map<AccountType, Map<User, BigDecimal>> getMapWithAccountTypeKeyAndSumMoneyForManInPLN() {
        return getCompanyStream()
                .flatMap(company -> company.getUsers().stream())
                .filter(isMan)
                .flatMap(user -> user.getAccounts().stream())
                .map(Account::getType)
                .collect(Collectors.toMap(accountType -> accountType, this::manWithSumMoneyOnAccounts));
    }

Ta metoda jest OK. Ale zastanawia mnie jeszcze metoda manWithSumMoneyOnAccounts(), ponieważ napisałem ją w taki sposób:

    private Map<User, BigDecimal> manWithSumMoneyOnAccounts(AccountType accountType) {
        return getUserStream() \\ 1
                .flatMap(user -> user.getAccounts().stream()) \\ 2
                .filter(account -> account.getType().equals(accountType)) \\ 3
                .collect(Collectors.toMap(
                        Function.identity(), \\ 4
                        this::getSumUserAmountInPLN \\ 5
                ));
    }

Prawdopodobnie nie do końca rozumiem jak działają strumienie, bo wydawało mi się, że:

  1. mam stream użytkowników
  2. mam stream i użytkowników i kont (nie samych kont jak w przypadku .map)
  3. filtruję dla jakich typów kont będę obliczał sumę na koncie
  4. tutaj zaczyna się problem, bo miałem nadzieję, że zostanie podstawiony obiekt User dlatego, że w całym strumieniu mam i użytkowników i konta, także powinien wnioskując z typu podstawić właśnie Usera.
  5. Konto zawiera kwotę także mając w strumieniu również konta powinien zostać przekazany BigDecimal z wartością konta.
    W tej metodzie schodzę z poziomu wyższego na niższy, bo tak jak napisałem w pierwszym poście

User zawiera List<account> accounts czyli użytkownik zawiera listę kont,
Account zawiera AccountType type (AccountType to enum) i Account zawiera Currency currency (Currency to też enum) czyli konto zawiera typ konta i > walutę, zawiera także kwotę rachunku w typie BigDecimal

więc raczej nie działa to w drugą stronę czyli zaczynam metodę od return accountType i tutaj w jakiś sposób mogę dostać się do klas prezentujących obiekty wyżej (zawierające właśnie ten accountType). Ale jest już blisko. :)

1

mam stream i użytkowników i kont (nie samych kont jak w przypadku .map)

Nie nie nie. flatMap służy po prostu do pakowania strumienia kolekcji w jeden strumień, ale mimo wszystko działa jak map więc aplikuje funkcje na argumencie i do tego argumentu już dalej nie ma dostępu!
map robi: Stream<T> -> Stream<S> za pomocą funkcji T->S
flatMap robi: Stream<T> -> Stream<S> za pomocą funkcji T -> Stream<S>, najpierw każde T zamienia na osoby Stream<S> a potem składa je w jeden strumień

Taka moja osobista rada: unikać przepychania typów podstawowych, bo łatwo potem o pomyłkę. Jak masz jakieś Map<String,Map<UUID,Map<UUID, BigDecimal>>> to potem trudno ogarnąć co dokładnie tam masz. Opakowuj takie rzeczy w obiekty domenowe. Analogicznie zamiast długiej listy dziwnych transformacji na strumieniu, zrób kilka nazwanych i jasnych.

getCompanyStream()
    .flatMap(company -> company.getUsers().stream())

Czemu nie metoda getUsers?

getCompanyStream()
    .flatMap(company -> company.getUsers().stream())
    .filter(isMan)
    .flatMap(user -> user.getAccounts().stream())

czemu nie getAccounts?
Zaręczam ze łatwiej sie coś takiego czyta.

1 użytkowników online, w tym zalogowanych: 0, gości: 1