Et si on passait enfin à Java 8 ?

L'objectif de cet article est de démystifier un peu les lambdas en Java. Vous montrez que finalement ce n'est pas grand chose à part un peu de simplification de syntaxe qu'on a appliqué à beaucoup de cas.

Si on revient à l'ancien temps (enfin pas si lointain que ça, ça fait à peine quatre ans que Java 8 est sorti (mars 2014)), les lambdas n'existaient pas, mais on faisait déjà tout sans elles.

Par exemple, disons que l'on souhaite manipuler une liste de personnage de série. Pouvoir les trier de différente façons, pouvoir n'afficher que ceux qui ont certains, etc. On va commencer par définir notre structure puis on va travailler sur la liste de nos données.

Voici la structure qu'on utilisera tout au long de l'article :

// Character.java

public class Character {
  private String name;
  private int apparitionYear;
  private String actor;
  private String show;

  // ...
  // constructor
  // getter/setter
  // toString
}

Et la liste de personnage qu'on utilisera :

List<Character> characters = Arrays.asList(new Character[] {
  new Character("First Doctor", 1963, "William Hartnell", MALE, "classic"),
  new Character("Second Doctor", 1966, "Patrick Troughton", MALE, "classic"),
  new Character("Third Doctor", 1970, "Jon Pertwee", MALE, "classic"),
  new Character("Fourth Doctor", 1974, "Tom Baker", MALE, "classic"),
  new Character("Fifth Doctor", 1981, "Peter Davison", MALE, "classic"),
  new Character("Sixth Doctor", 1984, "Colin Baker", MALE, "classic"),
  new Character("Seventh Doctor", 1987, "Sylvester McCoy", MALE, "classic"),
  new Character("Eighth Doctor", 1996, "Paul McGann", MALE, "TV film"),
  new Character("War Doctor", 2013, "John Hurt", MALE, "2005 show"),
  new Character("Ninth Doctor", 2005, "Christopher Eccleston", MALE, "2005 show"),
  new Character("Tenth Doctor", 2005, "David Tennant", MALE, "2005 show"),
  new Character("Eleventh Doctor", 2010, "Matt Smith", MALE, "2005 show"),
  new Character("Twelfth Doctor", 2013, "Peter Capaldi", MALE, "2005 show"),
  new Character("Thirteenth Doctor", 2017, "Jodie Whittaker", FEMALE, "2005 show"),
  new Character("River Song", 2008, "Alex Kingston", FEMALE, "2005 show"),
  new Character("Jack Harkness", 2005, "John Barrowman", MALE, "2005 show"),
  new Character("Rose Tyler", 2005, "Billie Piper", FEMALE, "2005 show"),
  new Character("Martha Jones", 2007, "Freema Agyeman", FEMALE, "2005 show"),
  new Character("Donna Noble", 2006, "Catherine Tate", FEMALE, "2005 show"),
  new Character("Amelia « Amy » Pond", 2010, "Karen Gillan", FEMALE, "2005 show"),
  new Character("Clara Oswald", 2010, "Jenna Coleman", FEMALE, "2005 show"),
  new Character("Nardole", 2015, "Matt Lucas", MALE, "2005 show"),
  new Character("Bill Potts", 2017, "Pearl Mackie", FEMALE, "2005 show")
});

Maintenant passons aux choses sérieuses et jouons un peu avec ces données !

Au besoin, les sources Java sont disponibles ici : https://gist.github.com/kuroidoruido/5454954cad1133e9ce0a568f28525b74.

Et si on affichait tout ça ?🔗

La première chose à faire c'est de vérifier que toutes nos données sont correctes. Et pour ça on va afficher toutes les données en utilisant la méthode toString() de Character.

En Java 7 : avec un foreach🔗

for(Character c : characters) {
  System.out.println(c);
}

Ici on n'appelle pas directement toString() parce qu'on sait que println() attend une String en paramètre et que l'appelle à toString() est fait implicitement dans ce cas là.

En Java 8 : la méthode forEach()🔗

La transformation la plus basique est de simplement passer par la méthode .forEach() de l'interface Iterable. Cette méthode attend une instance de Consumer qui va simplement être capable de traiter un par les éléments de la liste.

Version sans lambda :🔗

characters.forEach(new Consumer<Character>() {
  public void accept(Character c) {
    System.out.println(c);
  }
});

Ici on crée une instance de l'interface Consumer en même temps que l'on redéfinit le comportement de la méthode accept() en passant par une classe abstraite. On voit donc que trois concepts ressorte fortement : implémentation d'une interface, création d'une classe abstraite et surcharge de méthode.

Version avec lambda :🔗

characters.forEach(c -> { System.out.println(c); });
// or
characters.forEach(c -> System.out.println(c));

Ici on utilise une lambda pour faire exactement la même chose que pour la version sans lambda, mais on se concentre sur l'essentiel : passer chaque personnage à la méthode println().

À noter que les deux versions (avec et sans accolades) sont strictements equivalentes. Pouvoir omettre les accolades est un sucre syntaxique offert par Java pour ce genre de cas où on effectue une seule et unique action et où la valeur de retour de cette action est de type compatible avec le type attendu en retour de la lambda (ici Concumer.accept() renvoi void, et System.out.println() aussi, donc il n'y a pas de problème).

Version avec référence de méthode :🔗

characters.forEach(System.out::println);

Dans cette dernière variante, on utilise une référence de méthode. Dans version avec lambda, tout ce que faisait notre lambda c'est prendre le paramètre et le passer en paramètre d'une fonction directement. Dans ce cas de figure on peut donc directement omettre la lambda et indiquer à la méthode forEach() de passer directement la valeur à println().

Trier toutes ces données🔗

Maintenant que l'on a vu comment afficher les données, on va essayer de trier les personnages pour les avoir non pas dans l'ordre d'insertion, mais dans l'ordre alphabétique et aussi par ordre d'apparition dans la série (deux affichages différents).

Version Java 7 : l'interface Comparator<T>🔗

System.out.println("