5
votes

Trouvez des films où un acteur avec le prénom et le nom a travaillé en utilisant Java 8 Streams, mappez, filtrez, réduisez

J'essaie de jouer avec l'API Java 8 Stream et je voulais convertir la méthode suivante en utilisant la réduction de la carte de filtre de flux Java 8.

J'ai une liste de films et chaque objet Movie a une liste d'acteurs ainsi que d'autres champs .

Je veux trouver tous les films dans lesquels l'acteur avec un prénom et un nom spécifiques a travaillé.

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

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

Ma question est de savoir comment réécrire ce code en utilisant les 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 la mappe à plat et dans sa liste d'acteurs, comment puis-je retrouver la liste des films où seul cet acteur avec le prénom et le nom existe?


0 commentaires

4 Réponses :


2
votes

Trouver le premier élément correspondant en boucle sur un itérable et le casser une fois qu'il est trouvé peut facilement être réalisé en utilisant l'opération de terminal de court-circuitage 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 préfère vous suggérer d'utiliser des prédicats en ligne au lieu de les définir séparément à moins que vous ne les réutilisiez ailleurs. Cela conduit à un code plus condensé qui est moins verbeux. Voici à quoi ça ressemble.

movies.stream()
    .filter(m -> m.getActors().stream()
        .anyMatch(firstNamePredicate.and(lastNamePredicate)))
    .collect(Collectors.toList());

Pour une raison quelconque, si vous avez vraiment besoin d'utiliser les prédicats prédéfinis comme indiqué dans votre déclaration de problème, vous pouvez le faire comme ceci,

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


0 commentaires

2
votes

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());


0 commentaires

3
votes

Étant donné que les autres réponses abordaient déjà les moyens de résoudre le problème dans java-8 , avec cette solution, vous pouvez utiliser le tout nouveau Collecteurs .filtering introduit dans java-9 .Il suffit donc de 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()));


1 commentaires

Alternative intéressante. Pour rappel, Les collecteurs filtering () sont particulièrement utiles lorsqu'ils sont utilisés dans une réduction à plusieurs niveaux, comme en aval d'un groupingBy ou partitioningBy < / code> .



1
votes

Et encore une solution.

Parfois, l'utilisation du mauvais type de collection vous rend la vie difficile. Je suggérerais que Movie.getActors () renvoie un Set au lieu d'une List . 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());
}


0 commentaires