Trouvez des films où un acteur avec le prénom et le nom a travaillé en utilisant Java 8 Streams, map, filter, reduce


J'essaie de jouer avec Java 8 Stream API et je voulais convertir la méthode suivante en utilisant Java 8 stream filter map reduce.

J'ai une liste de Films et chaque objet de film a une liste d'acteurs avec d'autres champs.

Je veux trouver tous les films où l'acteur avec un prénom et un nom a travaillé dans l'informatique.

La méthode ci-dessous est basée sur Java 7 où je boucle sur la liste des films, puis boucle sur une liste d'acteurs pour cela film. Si un acteur avec ce prénom et ce nom est trouvé, je casse la boucle intérieure et ajoute ce film à la liste des films renvoyés.

Le code commenté fonctionne et je peux obtenir la bonne liste de films.

Ma question est de savoir comment puis-je réécrire ce code en utilisant des flux Java 8. Je peux voir que c'est une carte, filtrer, réduire le problème mais je ne suis pas en mesure de trouver une solution claire.

public List<Movie> getMoviesForActor(String firstName, String lastName) {

    final List<Movie> allMovies = movieRepository.getAllMovies();
    final Predicate<Actor> firstNamePredicate = actor -> actor.getFirstName().equalsIgnoreCase(firstName);
    final Predicate<Actor> lastNamePredicate = actor -> actor.getLastName().equalsIgnoreCase(lastName);

    final List<Movie> movies = new ArrayList<>();
    //        for (Movie movie : allMovies) {
    //            boolean actorFound = false;
    //            for (Actor actor : movie.getActors()) {
    //                if(firstName.equalsIgnoreCase(actor.getFirstName()) && lastName.equalsIgnoreCase(actor.getLastName())) {
    //                    actorFound = true;
    //                    break;
    //                }
    //            }
    //            if(actorFound) {
    //                movies.add(movie);
    //            }
    //        }

    final List<Actor> actors = allMovies.stream()
            .flatMap(
                    movie -> movie.getActors().stream().filter(firstNamePredicate.and(lastNamePredicate))
            ).collect(Collectors.toList());
    return movies;
}

Si je diffuse sur les films et flatmap et dans ce flux la liste des acteurs, comment puis-je obtenir la liste des films à nouveau où seul cet acteur avec prénom et nom existe?

Author: Naman, 2019-02-08

4 answers

Trouver le premier élément correspondant tout en bouclant sur un itérable et en cassant une fois qu'il est trouvé peut facilement être réalisé en utilisant l'opération de terminal de court-circuit anyMatch dans Java8. Passez ensuite le résultat de anyMatch à l'opérateur filter pour obtenir tous les films correspondant aux critères donnés.

Je vous suggère plutôt d'utiliser des prédicats en ligne au lieu de les définir séparément, sauf si vous les réutilisez ailleurs. Cela conduit à un code plus condensé qui est moins verbeux. Voici comment il regarder.

movies.stream()
    .filter(m -> m.getActors().stream()
        .anyMatch(
            a -> a.getFirstName().equalsIgnoreCase(firstName) 
                && a.getLastName().equalsIgnoreCase(lastName)))
    .collect(Collectors.toList());

Pour une raison quelconque, si vous avez vraiment besoin d'utiliser les prédicats prédéfinis tels que donnés dans votre énoncé de problème, vous pouvez le faire comme suit,

movies.stream()
    .filter(m -> m.getActors().stream()
        .anyMatch(firstNamePredicate.and(lastNamePredicate)))
    .collect(Collectors.toList());
 2
Author: Ravindra Ranwala, 2019-02-08 15:17:44

Puisque les autres réponses déjà abordé de façons dont vous pouvez résoudre le problème en java-8, avec cette solution, vous pouvez utiliser le tout nouveau Collectors.filtering introduit dans java-9.Donc, juste le laisser ici pour référence future.

List<Movie> movies = allMovies.stream()
                .collect(Collectors.filtering(
                      m -> m.getActors().stream().anyMatch(firstNamePredicate.and(lastNamePredicate)),
                Collectors.toList()));
 3
Author: Fullstack Guy, 2019-02-08 05:57:09

Une meilleure façon (fonctionnelle) de l'écrire avec votre code existant serait:

final Predicate<Movie> movieIncludesActor = movie -> movie.getActors()
        .stream()
        .anyMatch(firstNamePredicate.and(lastNamePredicate)); // check both the condition for all actors
final List<Movie> movies = allMovies.stream()
        .filter(movieIncludesActor) // movie which has such an actor
        .collect(toList());
 2
Author: Naman, 2019-02-08 05:25:43

Et juste une solution de plus.

Parfois, l'utilisation du mauvais type de collection vous rend la vie difficile. Je suggère que Movie.getActors() renvoie un Set<Actor> au lieu d'un List<Actor>. Cela rendrait le traitement beaucoup plus facile.

private class Movie {
    public Set<Actor> getActors() {
        return null;
    }
}

private class Actor {
    private final String firstName;
    private final String lastName;

    private Actor(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Actor)) return false;
        Actor actor = (Actor) o;
        return firstName.equals(actor.firstName) &&
                lastName.equals(actor.lastName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName);
    }
}

final List<Movie> allMovies = Collections.EMPTY_LIST;

public List<Movie> getMoviesForActor(String firstName, String lastName) {
    Actor actor = new Actor(firstName, lastName);

    return allMovies.stream()
            .filter(m -> m.getActors().contains(actor))
            .collect(Collectors.toList());
}
 1
Author: OldCurmudgeon, 2019-02-08 06:35:13