A la découverte des lambdas

logo.png

https://github.com/Ninja-Squad/ninjackaton-lambda/

Qui utilise Java5 ?

Qui utilise Java6 ?

Qui utilise Java7 ?

Java7 a changé ma façon de développer!

No one - ever
Notes

Qu’y a-t-il eu de nouveau dans Java 7? Quelques APIs: nio2, quelques ajouts dans concurrency, JDBC 4.1… Mais au quotidien, qu’est-ce qui a changé?

Project Coin !

List<String> list = new ArrayList<>();
switch(variable){

    case "ninja": ...

}
0b11000011

1_000_000
try {
    ...
}
catch (SQLException | IOException e) {
    log(e);
}
try (InputStream in = new FileInputStream(inputFile);
     OutputStream out = new FileOutputStream(outputFile)) {
    ...
}
// automatically closed!

Sortie de Java8 ?

Quelles sont les nouveautés ?

Notes
lambda.png

Vers un peu de fonctionnel!

hipster-ariel-irl.jpg
  List<Integer> list = Arrays.asList(1, 2, 3);
  List<String> transformed = new ArrayList<>();

  for (Integer i : list) {
      transformed.add(String.valueOf(i * 1000));
  }

  System.out.println(transformed);
var array = [1, 2, 3];

var transformed = $.map(array, function(value) {
    return (value * 1000) + " ";
});

console.log(transformed);
List<Integer> list = Arrays.asList(1, 2, 3);

List<String> transformed =
    FluentIterable.from(list)
                  .transform(new Function<Integer, String>() {
                      @Override
                      public String apply(Integer input) {
                          return String.valueOf(input * 1000);
                      }
                  })
                  .toList();

System.out.println(transformed);
Notes

C’est assez lourd. On perd du temps à écrire la classe anonyme, il y a une instanciation de classe à chaque fois, le code est peu lisible, et finalement, les seules parties intéressantes sont l’appel à transform(), et l’appel à String.valueOf(). Le reste, c’est du boilerplate.

List<Integer> list = Arrays.asList(1, 2, 3);

List<String> transformed =
    list.stream()
        .map(input -> String.valueOf(input * 1000))
        .collect(Collectors.toList());

System.out.println(transformed);
Notes

Mais finalement, quel est l’intérêt par rapport à une simple boucle for? Supposons qu’on ait une liste de personnes, et qu’on veuille ne garder dans cette liste que les hommes, qu’on veuille ensuite les trier par ordre alphabétique des noms et prénoms, et ne conserver que les 2 premiers. <<<<<<< HEAD <<<<<<< HEAD

private static void extractWithJava7() {
    List<Person> males = new ArrayList<>();

    for (Person person : Person.NINJA_SQUAD) {

    }
}
private static void extractWithJava7() {
    List<Person> males = new ArrayList<>();

    for (Person person : Person.NINJA_SQUAD) {
        if (person.getGender() == Person.Gender.MALE) {
            males.add(person);
        }
    }
}
private static void extractWithJava7() {
    List<Person> males = new ArrayList<>();

    for (Person person : Person.NINJA_SQUAD) {
        if (person.getGender() == Person.Gender.MALE) {
            males.add(person);
        }
    }
    Collections.sort(males, new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {

        }
    });
}
private static void extractWithJava7() {
    List<Person> males = new ArrayList<>();

    for (Person person : Person.NINJA_SQUAD) {
        if (person.getGender() == Person.Gender.MALE) {
            males.add(person);
        }
    }
    Collections.sort(males, new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            int result =
                o1.getLastName()
                  .compareTo(o2.getLastName());
            if (result == 0) {
                result =
                    o1.getFirstName()
                      .compareTo(o2.getFirstName());
            }
            return result;
        }
    });
}
private static void extractWithJava7() {
    List<Person> males = new ArrayList<>();

    for (Person person : Person.NINJA_SQUAD) {
        if (person.getGender() == Person.Gender.MALE) {
            males.add(person);
        }
    }
    Collections.sort(males, new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            int result =
                o1.getLastName()
                  .compareTo(o2.getLastName());
            if (result == 0) {
                result =
                    o1.getFirstName()
                      .compareTo(o2.getFirstName());
            }
            return result;
        }
    });
    List<Person> result = males.subList(0, 2);

    System.out.println("result = " + result);
}
private static void extractWithJava8() {
    List<Person> result =
        Person.NINJA_SQUAD
            .stream()

    System.out.println("result = " + result);
}
private static void extractWithJava8() {
    List<Person> result =
        Person.NINJA_SQUAD
            .stream()
            .filter(p -> p.getGender() == Person.Gender.MALE)

    System.out.println("result = " + result);
}
private static void extractWithJava8() {
    List<Person> result =
        Person.NINJA_SQUAD
            .stream()
            .filter(p -> p.getGender() == Person.Gender.MALE)
            .sorted(Comparators
                .<Person, String>comparing(Person::getLastName)
                .thenComparing(Person::getFirstName))

    System.out.println("result = " + result);
}
private static void extractWithJava8() {
    List<Person> result =
        Person.NINJA_SQUAD
            .stream()
            .filter(p -> p.getGender() == Person.Gender.MALE)
            .sorted(Comparators
                .<Person, String>comparing(Person::getLastName)
                .thenComparing(Person::getFirstName))
            .substream(0, 2)

    System.out.println("result = " + result);
}
private static void extractWithJava8() {
    List<Person> result =
        Person.NINJA_SQUAD
            .stream()
            .filter(p -> p.getGender() == Person.Gender.MALE)
            .sorted(Comparators
                .<Person, String>comparing(Person::getLastName)
                .thenComparing(Person::getFirstName))
            .substream(0, 2)
            .collect(Collectors.toList());

    System.out.println("result = " + result);
}

How does it work ?

Interfaces can now have

/**
 * Sort this list using the supplied {@code Comparator}
 * to compare elements.
 *
 * @param c the {@code Comparator} used to compare list elements
 */
public default void sort(Comparator<? super E> c) {
    Collections.<E>sort(this, c);
}
@FunctionalInterface
Notes

Chaque fois qu’on a une interface avec une seule méthode abstraite, on peut en créer une instance avec une lambda. Exemple: FileFilter, Runnable, Callable, ActionListener, Comparator, etc.

File[] textFiles =
    directory.listFiles(f -> f.getName().endsWith(".txt"));
Notes

C’est ce qui permet d’avoir une méthode stream() dans toutes les collections (même la vôtre), une méthode addAll() dans toutes les collections (même la vôtre). Backward compatibility! Les méthodes default ne peuvent pas être final, donc on peut toujours les redéfinir.

Exemples

interface Concatenator {
    String concat(int a, double b);
}

donne

(int a, double b) -> {
    String s = a + " " + b;
    return s;
}
interface Concatenator {
    String concat(int a, double b);
}

ou

(int a, double b) -> return a + " " + b;
interface Concatenator {
    String concat(int a, double b);
}

ou

(int a, double b) -> a + " " + b;
interface Concatenator {
    String concat(int a, double b);
}

ou

(a, b) -> a + " " + b;
interface UnaryOperator {
    int op(a);
}

donne

(a) -> a * a;
interface UnaryOperator {
    int op(a);
}

ou sans parenthèses

a -> a * a;
interface NumberSupplier {
    int get();
}

donne

() -> 25;
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à une méthode

String::length
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à une méthode

String::length

identique à

s -> s.length()
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à un constructeur

Integer::new
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à un constructeur

Integer::new

identique à

s -> new Integer(s)
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à une méthode statique

Integer::parseInt
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à une méthode statique

Integer::parseInt

identique à

s -> Integer.parseInt(s)
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à une méthode sur un autre objet

stringToIntMap::get
interface StringToIntFunction {
    int toInt(String s);
}

avec une référence à une méthode sur un autre objet

stringToIntMap::get

identique à

s -> stringToIntMap.get(s)

Effectively final ?

List<Integer> incrementAllWith(List<Integer> list, int inc) {
    return list.stream()
               .map(i -> i + inc)
               .boxed()
               .collect(Collectors.toList());
}

Exemple de code interdit

int cumulatedAge = 0;
persons.forEach(p -> {
    cumulatedAge += p.getAge();
});

Remplacer par

int cumulatedAge =
    persons.stream().map(Person::getAge).sum();

a Stream ?

1 000 000 tweets?

public static int maxRetweets(Set<Tweet> tweets) {
    return tweets.parallelStream()
                 .map(t -> t.getRetweetCount())
                 .max();
}

How does it work ?

public class DecompilationTest {
    public static void main(String[] args) {

        Function<Integer, String> function = i -> i.toString();

        System.out.println("function.getClass().getName() = "
                           + function.getClass().getName());
    }
}
public static void main(String[] args)
                            throws ClassNotFoundException {

    Class.forName(
        "com.ninja_squad.lambdademo.DecompilationTest$$Lambda$1");

    Function<Integer, String> function = i -> i.toString();

    System.out.println("function.getClass().getName() = "
                       + function.getClass().getName());
}
surprise.gif

ClassNotFoundException !

public static void main(String[] args)
                            throws ClassNotFoundException {

    Function<Integer, String> function = i -> i.toString();

    Class.forName(
        "com.ninja_squad.lambdademo.DecompilationTest$$Lambda$1");

    System.out.println("function.getClass().getName() = "
                       + function.getClass().getName());
}

javap -cp . -c -p com.ninja_squad.lambdademo.DecompilationTest

public class com.ninja_squad.lambdademo.DecompilationTest {
  public com.ninja_squad.lambdademo.DecompilationTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.ClassNotFoundException;
    Code:
       0: invokedynamic #2,  0              // InvokeDynamic #0:lambda:()Ljava/util/function/Function;
       5: astore_1
       6: return

  private static java.lang.String lambda$0(java.lang.Integer);
    Code:
       0: aload_0
       1: invokevirtual #3                  // Method java/lang/Integer.toString:()Ljava/lang/String;
       4: areturn
}

Conclusion

Notes

Expérience très limitée pour le moment. - Encore plus que pour les classes anonymes, le corps des lambda devrait être très très court. Déléguer à des méthodes si le corps fait plus d’une ou deux lignes. - Réduit significativement le boilerplate. - Idée géniale: toute interface fonctionnelle peut être implémentée sous forme de lambda. Ca va permettre d’utiliser les lambdas avec Guava et des tas d’autres APIs, sans attendre une quelconque évolution de Guava. - Problème principal en ce moment: l’inférence de type. Ce serait bien de pouvoir écrire .collect(Collectors.toList()) plutôt que .collect(Collectors.<String>toList()). Travail toujours en cours pour améliorer ça. - De nouveaux messages cryptiques du compilateur à comprendre. Pas facile facile. - C’est une cible mouvante. On a dû réécrire nos slides 5 fois parce qu'à chaque fois qu’on revenait dessus, tout avait changé. Ca devrait se stabiliser d’ici peu. - Manque de méthodes raccourcis. Par exemple: .toList() au lieu de .collect(Collectors.<String>toList()) - Manque cruel de javadoc. Mais ça va s’améliorer. - Les IDEs (en tout cas IntelliJ) ne sont pas encore au point, mais le sujet est instable, donc c’est normal. Fausses inférences, signalement d’erreurs quand il n’y en a pas. Pas de signalement d’erreur quand il y en a, etc.

Le developer review va bientôt commencer. A nous d’agir. Déjà maintenant, le feedback est le bienvenu.