Skip to content

Instantly share code, notes, and snippets.

@bastienapp
Created July 27, 2026 05:06
Show Gist options
  • Select an option

  • Save bastienapp/c117caff2dcf745daa3766b5473b653e to your computer and use it in GitHub Desktop.

Select an option

Save bastienapp/c117caff2dcf745daa3766b5473b653e to your computer and use it in GitHub Desktop.
Filter sans les Streams

Avant de voir comment utiliser les Stream pour filtrer une liste, voyons comment procéder sans eux.

Partons d'une liste d'entiers contenant les nombres de 0 à 6.

List<Integer> list = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6));

Tu veux filtrer la liste pour ne garder que les nombres pairs. Tu vas devoir parcourir chaque élément de la liste et vérifier s'il est pair. Si c'est le cas, il faudra le stocker dans une autre liste.

01. Itération

List<Integer> listFiltered = new ArrayList<>();

for (Integer item : list) {
    if (item % 2 == 0) {
        listFiltered.add(item);
    }
}

Si tu affiches le contenu de la nouvelle liste filtrée, tu obtiendras le résultat suivant :

System.out.println(listFiltered.toString());

[0, 2, 4, 6]

02. Créer une méthode

Tu peux maintenant isoler le code permettant de filtrer la liste dans une méthode, de la façon suivante :

private static List<Integer> filter(List<Integer> list) {
    List<Integer> listFiltered = new ArrayList<>();
    for (Integer item : list) {
        if (item % 2 == 0) {
            listFiltered.add(item);
        }
    }
    return listFiltered;
}

Tu pourras désormais filtrer n'importe quelle liste d'entiers de la façon suivante :

List<Integer> listFiltered = filter(list);

Predicate

Dans l'exemple précédent, la condition de filtrage est contenue dans la méthode filter. Il s'agit de la ligne suivante :

if (item % 2 == 0)

Imaginons que tu veuilles améliorer ta méthode afin de lui passer la condition de filtrage en paramètre. Tu pourrais ainsi utiliser la même méthode filter pour filtrer les nombres pairs, les multiples de trois, les nombres supérieurs à 10, etc.

Il va te falloir appliquer une fonction de test à chacun des nombres. En Java, on peut résoudre cette problématique en créant une interface.

01. Créer son interface de filtrage

Ton interface va déterminer si l'élément doit être conservé ou non dans la liste. Dans cet exemple, elle va s'appeler Keep. Cette interface ne comportera qu'une seule signature de méthode : test. En fonction de l'entier reçu en argument, elle indiquera s'il doit être conservé ou non en retournant un booléen :

private interface Keep {

    boolean test(Integer item);
}

Comme il s'agit d'une interface, on ne donne pas de corps à la méthode. C'est logique : la manière de sélectionner l'élément dépendra du besoin. Comme indiqué précédemment, on pourrait vouloir conserver les nombres pairs dans un cas ou les multiples de trois dans un autre.

Il va te falloir modifier ta méthode filter afin qu'elle accepte en argument une instance de l'interface Keep :

private static List<Integer> filter(List<Integer> list, Keep keep) {
    List<Integer> listFiltered = new ArrayList<>();
    for (Integer item : list) {
        if (keep.test(item)) {
            listFiltered.add(item);
        }
    }
    return listFiltered;
}

Cependant, il n'est pas possible de créer directement une instance d'une interface. Java te permet donc de créer une classe anonyme qui implémente une interface et en crée une instance en même temps :

new Keep() {

    @Override
    public boolean test(Integer item) {
        return item % 2 == 0;
    }
};

Tu peux alors passer la classe anonyme en paramètre de la méthode filter :

List<Integer> listFiltered = filter(list, new Keep() {

    @Override
    public boolean test(Integer item) {
        return item % 2 == 0;
    }
});

02. Interface fonctionnelle

Maintenant que tu as vu comment créer une interface et passer une classe anonyme en paramètre, sache que, depuis Java 8, il est possible d'utiliser plusieurs interfaces fonctionnelles déjà existantes.

Celle qui va nous intéresser ici est Predicate<T> :

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T var1);

    // a few other default methods here
}

Ici, T représente un type générique : il peut correspondre à n'importe quelle classe.

Un Predicate est utilisé pour effectuer un test. Si le test réussit, la méthode test retourne true. Sinon, elle retourne false.

Comme tu peux le remarquer, cette interface est presque identique à l'interface Keep créée précédemment. Tu peux donc créer une classe anonyme à partir de celle-ci, de la façon suivante :

List<Integer> listFiltered = filter(list, new Predicate<Integer>() {
    @Override
    public boolean test(Integer item) {
        return item % 2 == 0;
    }
});

Cependant, comme ta méthode filter s'attend à recevoir un objet de type Keep, il va également falloir remplacer cet argument par un Predicate :

private static List<Integer> filter(List<Integer> list, Predicate<Integer> keep) {
    List<Integer> listFiltered = new ArrayList<>();
    for (Integer item : list) {
        if (keep.test(item)) {
            listFiltered.add(item);
        }
    }
    return listFiltered;
}

Lambdas

Depuis Java 8, l'utilisation des interfaces fonctionnelles a rendu nécessaire une approche moins verbeuse que leur instanciation sous forme de classes anonymes.

C'est pourquoi les lambdas (ou fonctions fléchées) ont été introduites. Elles permettent de simplifier considérablement une classe anonyme créée à partir d'une interface fonctionnelle, uniquement lorsque celle-ci ne contient qu'une seule méthode à implémenter.

Prenons l'exemple de notre Predicate lors de l'appel de la méthode filter :

filter(list, new Predicate<Integer>() {
    @Override
    public boolean test(Integer item) {
        return item % 2 == 0;
    }
});

La méthode filter sait déjà qu'elle attend un Predicate en argument : il est donc inutile de le préciser. De plus, l'interface Predicate ne contient qu'une seule méthode à implémenter : il n'est pas nécessaire de préciser son nom non plus.

Une lambda se présente sous la forme suivante :

(parameters, ...) -> {
    // body
}

Dans le cas du passage d'un Predicate à la méthode filter, il devient donc possible d'écrire le code de la façon suivante :

(item) -> {
    return item % 2 == 0;
}

Mais ce n'est pas tout : il est possible de le simplifier encore davantage ! En effet, si une lambda ne possède qu'un seul paramètre, il est possible de retirer les parenthèses. De plus, si son corps ne contient que le retour d'une expression, il est possible de retirer les accolades et le mot-clé return. Le résultat sera alors retourné implicitement :

item -> item % 2 == 0

Il est alors possible de simplifier l'appel de filter pour obtenir le code suivant :

List<Integer> listFiltered = filter(list, item -> item % 2 == 0);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment