4 votes

Optimisation de l'obtention d'une correspondance à partir d'une liste

Supposons que j'aie la classe suivante :

class Person { 
   private String firstName;
   private String lastName;
   public Person(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
   }
   // getters and setters
}

Supposons que je dispose d'une liste de Person et je veux trouver un objet Personne dans la liste dont le prénom est Jean .

Le code le plus court auquel je peux penser pour faire ce travail est le suivant :

personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .collect(Collectors.toList())
    .get(0);

Comme vous pouvez le constater, ce n'est pas si court. Pouvez-vous imaginer un moyen plus court ?


Editer : Certaines personnes recommandent d'utiliser findFirst() . Dans ce cas, le code sera :

personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .findFirst()
    .get()

Il est seulement un peu plus court.

2voto

Zabuza Points 11045

Stream#findAny

Vous pouvez utiliser Stream#findAny pour cela. Il renvoie un Optional<Person> vide au cas où il n'y aurait pas de résultat positif.

Optional<Person> person = personStream.filter(p -> p.getFirstName().equals("John")).findAny();

De son [la documentation](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#findAny()) :

Retourne un Optional décrivant un élément du flux, ou un vide Optional si le flux est vide.

Il y a aussi findFirst mais findAny comporte deux caractères de moins et sa mise en œuvre est potentiellement plus rapide.


Référence de la méthode

Le code est plus court si vous placez le filtre dans une méthode spécifique :

public static boolean isJohn(Person p) {
    return "John".equals(p.getFirstName());
}

Le code devient alors

Optional<Person> person = personStream.filter(MyClass::isJohn).findAny();

Prédicat

Si vous placez le filtre dans un prédicat dédié, il est encore plus court :

Predicate<Person> isJohn = p -> "John".equals(p.getFirstName());

On obtient alors

Optional<Person> person = personStream.filter(isJohn).findAny();

Le plus court possible

Sacrifier la lisibilité au profit d'un code court ( ne jamais faire cela ), nous pouvons remplacer les noms des variables et obtenir :

Optional<Person> p = s.filter(j).findAny();

Notes

Préférer comparer "John" contre le nom de la personne, et non l'inverse. De cette manière, il est null -sûr. Vous pouvez également utiliser Objects#equals pour obtenir null -Sécurité. C'est-à-dire

// Not null-safe
p.getFirstName().equals("John");

// Null safe
"John".equals(p.getFirstName());
Objects.equals("John", p.getFirstName());

Ne pas appeler get() sur Optional sans prouver que l'appel ne se bloquera pas. Le non-respect de cette règle va à l'encontre de l'objectif de Optional . Préférez plutôt l'option orElseXXX méthodes. Ou au moins protéger l'accès avec un if (result.isEmpty()) .

2voto

Slanec Points 14354

Bibliothèque standard

Avec seulement la bibliothèque standard, utilisez ce que les autres réponses fournissent. C'est relativement court, clair, standard, tout le monde le comprendra. Elle vous oblige à gérer explicitement le cas où l'élément n'est pas présent, et c'est une bonne chose. Le seul élément "inutile" est le stream() et cela a aussi sa raison d'être sémantique subtile.

Person john = personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .findFirst()
    .get();

Bibliothèques tierces

Si vous pouvez utiliser une bibliothèque, il existe des solutions plus courtes (mais pas nécessairement meilleures). L'utilisation de Google Guava's Iterables.find() :

Person john = Iterables.find(personList, person -> person.getFirstName().equals("John"));

(Il est à noter qu'une exception est levée si l'élément n'est pas trouvé. Envisagez d'utiliser l'autre méthode find() avec une valeur par défaut, ou la méthode tryFind() méthode.)

La même chose est disponible dans de nombreuses autres bibliothèques, par exemple Collections Apache Commons : IterableUtils.find() .

La goyave mentionnée ci-dessus possède également les caractéristiques suivantes onlyElement() collecteur :

Person john = personList.stream()
    .filter(person -> person.getFirstName().equals("John"))
    .collect(MoreCollectors.onlyElement());

Une approche différente

La recherche d'un élément spécifique dans une liste est une opération linéaire, ou O(n). Si vous devez effectuer cette opération de manière répétée, il est clair que vous devez utiliser une structure de données différente. Peut-être qu'une table de correspondance entre les noms de personnes et les personnes serait plus utile ?

Map<String, Person> personsByFirstName = personList.stream()
    .collect(toMap(Person::getFirstName, Function.identity()));
Person john = personsByFirstName.get("John");

(Notez que cela ne permet de conserver qu'une seule personne par prénom. Ce n'est probablement pas ce que vous voulez, n'est-ce pas ?)

1voto

ernest_k Points 14807

Le fait d'être plus court n'est pas nécessairement mieux. Utilisez un code plus efficace ou plus lisible/maintenable.

Il est plus court, mais pire à bien des égards :

personList.removeIf(p -> !"John".equals(p.getFirstName()));

Stream.findFirst améliore l'efficacité en arrêtant le flux dès que le premier élément est trouvé :

personList.stream()
          .filter(person -> person.getFirstName.equals("John"))
          .findFirst() //<-- This will stop your stream when the first match is found
          .get();

Mais lorsque vous devez éviter d'appeler get sur un espace vide Optional Vous devrez donc l'allonger.

Ce ne sont que deux exemples qui montrent que l'on peut dégrader la qualité du code en essayant de le raccourcir à tout prix.

Il s'agit d'un compromis, qui dépend du développeur (comme tout le reste). À mon avis, le choix entre efficacité/lisibilité et brièveté du code est facile à faire.

0voto

M.Soldin Points 144

Il suffit d'utiliser Stream.findFirst(), mais ne pensez pas à ce qui est le plus court. Au lieu de cela, créez toujours le code qui est le plus facile à comprendre (dans ce cas, ce serait findFirst). Et je ne pense pas que 2 ou 3 caractères dans le code aient de l'importance.

    List<Person> test = new ArrayList<>();

    test.stream().filter(s -> s.getFirstName().equals("John")).findFirst();

0voto

TreffnonX Points 1744

Personnellement, je suggère de jeter un coup d'œil aux bibliothèques apache commons, qui fournissent de nombreux utilitaires utiles. http://commons.apache.org/ Le paquet Collections ( http://commons.apache.org/proper/commons-collections/ ) a un IterableUtils -qui vous permet d'effectuer les opérations suivantes :

Person john = IterableUtils.find(personList, person -> person.getFirstName().equals("John"));

En outre, il offre un grand nombre d'autres services utiles. Ou, pour reprendre votre exemple :

Predicate<Person> isJohn = p -> "John".equals(p.getFirstName());
Person john = IterableUtils.find(personList, isJohn);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X