functional way to accumulate pairs in java8

215 Views Asked by At

Here's some imperative code that I'm trying to translate into functional programming code:

public class Person {
    String name;
    Token token;

    public Person(String name, Token token) {
        this.name = name;
        this.token = token;
    }
}
public class Token {
    String id;
    boolean isValid;
    public Token(String id, boolean isValid) {
        this.id = id;
        this.isValid = isValid;
    }
    public String getId() { return id; }
    public boolean isValid() {return isValid;}
}
public static List<Token> getTokensForPerson(String name) {...}


public static List<Person> getPeople1 (String[] names) {

    List<Person> people = new ArrayList<Person> ();
    for (String name: names) {
        List<Token> tokens = getTokensForPerson(name);
        for (Token token: tokens) {
            if (token.isValid()) {
                people.add(new Person(name, token));
            }
        }

    }
    return people;
}

Here's my attempt to do it the functional way.

public static List<Person> getPeople2 (String[] names) {

    return Arrays.stream(names).map(name -> getTokensForPerson(name))
        .flatMap(tokens -> tokens.stream().filter(token -> token.isValid))
        .map(token -> new Person(name, token))   // <== compiler error here. "Cannot resolve symbol 'name'"
        .collect(Collectors.toList());
}

However it doesn't compile since in the last map operation I need to refer to name to create the Person object and name is not available at that time. Any ideas?

2

There are 2 best solutions below

0
On BEST ANSWER

You can move the map steps inside the flatMap:

return Arrays.stream(names)
        .<Person>flatMap(
                name -> getTokensForPerson(name).stream()
                        .filter(Token::isValid)
                        .map(token -> new Person(name, token)))
        .collect(Collectors.toList());

This way you can access name variable as well.

StreamEx-based solution is shorter, though it requires third-party library:

return StreamEx.of(names)
               .cross(name -> getTokensForPerson(name).stream())
               // Here we have the stream of entries 
               // where keys are names and values are tokens
               .filterValues(Token::isValid)
               .mapKeyValue(Person::new)
               .toList();
0
On

Would it be possible to create TokenExtended class extending Token, and adding the name, and return a List<TokenExtended> from getTokensForPerson instead of the List<Token>?

public class TokenExtended extends Token {
    private String name;
    public TokenExtended(String name, String id, boolean isValid) {
        super(id, isValid);
        this.name = name;
    }
}

This way your code would work

    Arrays.stream(names).map(name -> getTokensForPerson(name)).flatMap(tokens -> tokens.stream().filter(token -> token.isValid))
            .map(token -> new Person(token.name, token)).collect(Collectors.toList());