Comprendre les expressions lambda Java 8

Le type lambda est comme ça.

Runnable runner = () -> { System.out.println("Hello Lambda!"); };

Vous pouvez avoir peur si vous ne le connaissez pas, mais il est en fait configuré à l'aide des fonctionnalités Java existantes. Ici, décomposons l'équation lambda et comprenons-la depuis le début.

Cliquez ici pour l'abréviation de l'expression lambda. [Java] Résumé des omissions de l'expression lambda

1. Essayez de désassembler l'équation lambda

Les expressions Lambda utilisent un mécanisme appelé classes locales et classes anonymes.

1-1. Classe locale

Une classe locale est un mécanisme qui peut être utilisé en déclarant une classe lors du traitement d'une méthode.

public static void main(String[] args) {

  class Local {
    public void sayHello() {
      System.out.println("Hello!");
    }
  }

  Local local = new Local();
  local.sayHello(); // Hello!
}

Vous pouvez également définir une classe locale qui implémente l'interface.

public static void main(String[] args) {

  class Local implements Runnable {
    @Override
    public void run() {
      System.out.println("Hello Lambda!");
    }
  }

  Runnable runner = new Local();
  runner.run(); // Hello Lambda!
}

Ensuite, jetons un œil à la classe anonyme.

1-2. Classe anonyme

La classe anonyme est un mécanisme qui omet la déclaration de la classe locale qui implémente l'interface. Voici un exemple de classe anonyme qui implémente l'interface Runnable.

public static void main(String[] args) {

  Runnable runner = new Runnable() {
    @Override
    public void run() {
      System.out.println("Hello Lambda!");
    }
  };

  runner.run(); //Hello Lambda!
}

Il semble que vous créez une instance de l'interface Rannable, mais que vous créez en fait une instance d'une classe anonyme qui implémente l'interface Rannable. Enfin, regardons l'expression lambda.

1 à 3. Type Lambda

L'expression lambda est obtenue en omettant "new Runnable () {}" et "public void run" de la classe anonyme.

public static void main(String[] args) {

  Runnable runner = () -> { System.out.println("Hello Lambda!"); };
  runner.run(); //Hello Lambda!
}

Le premier () représente l'argument de la méthode run, et le contenu de-> {} est le contenu de l'implémentation de la méthode run. La variable runner se voit attribuer une instance d'une classe anonyme qui implémente Runnable. En d'autres termes, une expression lambda est une expression qui crée une instance qui implémente l'interface </ b>.

Au fait, si j'omets "new Runnable () {}", je ne sais pas quel type d'instance créer. Java dispose d'un mécanisme pour déduire automatiquement en fonction du type de variable à affecter. Ce mécanisme est appelé inférence de type </ b>.

Vous pouvez spécifier une interface comme argument pour définir une méthode qui peut accepter les expressions lambda.

public static void main(String[] args) {
    method(()->{System.out.println("Hello Lambda!");});
}
public static void method(Runnable r) {
    r.run();
}
// Hello Lambda!

Ce type est également déduit du type d'argument et une instance de type Runnable est automatiquement générée.

De plus, si vous omettez "public void run", vous ne saurez pas quelle méthode remplacer pour une interface qui a plusieurs méthodes définies. Par conséquent, les expressions lambda ne peuvent utiliser des interfaces qu'avec une seule méthode abstraite </ b>.

L'interface Rannable seule ne peut créer que des expressions lambda sans arguments et sans valeur de retour. Si vous souhaitez le créer sous une autre forme, une interface fonctionnelle a été ajoutée, alors utilisez-la.

2. Interface fonctionnelle

De nombreuses nouvelles interfaces ont été ajoutées sous le package java.util.function de SE8. Celles-ci sont appelées interfaces fonctionnelles. https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/package-summary.html

Les interfaces fonctionnelles sont un groupe d'interfaces qui ont une seule méthode et sont très pratiques à utiliser dans les expressions lambda. Parmi ceux-ci, nous présenterons les fonctions, les consommateurs et les prédicats fréquemment utilisés.

2-1. Function<T, R> Dans Function \ <T, R>, T spécifie le type de l'argument de la méthode et R spécifie le type de la valeur de retour. La méthode est R appliquer (T).

Function<Integer, String> asterisker = (i) -> { return "*"+ i; };
String result = asterisker.apply(10);
System.out.println(result); // *10

Il existe également une interface appelée BiFunction qui accepte deux arguments.

BiFunction<Integer, Integer, Integer> adder = (a, b) -> { return a + b; };
int result = adder.apply(1, 2);
System.out.println(result); // 3

2-2. Consumer<T> T dans Consumer \ spécifie le type d'argument de méthode. La méthode est nulle accept (T).

Consumer<String> buyer = (goods) -> { System.out.println(goods + "j'ai acheté"); };
buyer.accept("balle de riz"); // balle de rizを購入しました。

Je ne l'introduirai pas, mais il existe également une interface BiConsumer avec deux arguments et aucune valeur de retour.

2-3. Predicate<T> T dans Predicate \ spécifie le type d'argument de méthode. La méthode est le test booléen (T).

Predicate<String> checker = (s)-> { return s.equals("Java"); };
boolean result = checker.test("Java");
System.out.println(result); //true

3. Comment utiliser l'expression lambda

Pourquoi le style Lambda est-il né en premier lieu? C'est parce que je voulais changer le processus de manière dynamique en passant le processus comme argument au lieu d'une valeur. Des exemples typiques sont les méthodes Collections.sort et Stream API.

3-1. Collections.sort(List<T>, Comparator<? super T>) La méthode de tri dépend du type et de la situation de l'objet. Par exemple, même lors du tri des nombres, il peut être dans un ordre croissant simple ou dans un ordre croissant absolu. Afin de réaliser les deux types, il est nécessaire de basculer dynamiquement le processus de comparaison numérique. Par conséquent, Collections.sort est conçu pour recevoir le processus de comparaison lui-même.

int[] numbers = {-1, 2, 0, -3, 8};

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

for(int n : numbers) {
  numbersList.add(n);
}

Collections.sort(numbersList,[Méthode de tri]);

Pour [Méthode de tri], spécifiez l'instance qui implémente la méthode compare (s1, s2) de l'interface Comparer. La méthode compare renvoie un type int et détermine la magnitude comme suit.

Valeur de retour Grand et petit
Supérieur à 0 s1 > s2
0 s1 = s2
Moins de 0 s1 < s2

Trions en fait à l'aide de l'expression lambda.

Collections.sort(numbersList, (a, b) -> { return a - b; });

for(Integer n : numbersList) {
  System.out.print(n + " ");
}
// -3 -1 0 2 8

Vous pouvez modifier la méthode de tri en modifiant le contenu de l'expression lambda. Faisons-le par ordre décroissant.

Collections.sort(numbersList, (a, b) -> { return b - a; });

for(Integer n : numbersList) {
  System.out.print(n + " ");
}
// 8 2 0 -1 -3 

Essayons dans l'ordre des valeurs absolues.

Collections.sort(numbersList, (a, b) -> { return a*a - b*b; });

for(Integer n : numbersList) {
  System.out.print(n + " ");
}
// 0 -1 2 -3 8 

Bien sûr, cela fonctionne bien même si vous passez une instance d'une classe régulière qui implémente Comparator. Cependant, en utilisant l'expression lambda, il semble que le processus lui-même soit passé, il devient donc possible d'écrire de manière plus concise et intuitive.

3-2.StreamAPI Une méthode appelée stream a été ajoutée à l'interface Collection. La méthode stream renvoie sa propre instance Stream. Stream définit de nombreuses méthodes utiles qui prennent une interface fonctionnelle comme argument.

void forEach(Consumer<T>) La méthode forEach prend un Consumer comme argument et répète le processus pour le nombre d'éléments.

int[] numbers = {-1, 2, 0, -3, 8};

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

for(int n : numbers) {
  numbersList.add(n);
}

numbersList.stream().forEach((i) -> { System.out.print(i + " "); });
// -1 2 0 -3 8 

Stream filter(Predicate<T>) La méthode de filtrage prend Predicate comme argument et renvoie un Stream excluant ceux qui ne remplissent pas les conditions. Puisqu'il s'agit également d'un Stream qui est renvoyé, vous pouvez appeler la méthode ForEach telle quelle.

numbersList.stream().filter((i) -> { return i > 0; })
                    .forEach((i) -> { System.out.print(i + " "); });
                    // 2 8 

Étant donné que la valeur de retour de la méthode de filtrage est de type Stream, vous pouvez continuer à appeler forEach. Par conséquent, des méthodes telles que le filtre sont appelées des opérations intermédiaires. D'autre part, une méthode comme forEach est appelée une opération de terminaison.

Stream map(Function<T, R>) La méthode map prend une Function comme argument et renvoie le résultat traité sous forme de Stream. C'est une opération intermédiaire comme le filtre.

numbersList.stream().filter((i) -> { return i >= 0; })
                    .map((i) -> { return "*" + i + "*"; })
                    .forEach((s) -> { System.out.print(s + " "); });
                    // *2* *0* *8* 

Stream sorted(Comparator<T>) Bien qu'il s'agisse d'un package java.util, il fournit également une méthode triée qui prend un comparateur comme argument. C'est une opération intermédiaire comme le filtre.

numbersList.stream().filter((i) -> { return i >= 0; })
                    .sorted((i1, i2) -> { return i1 - i2; })
                    .map((i) -> { return "*" + i + "*"; })
                    .forEach((s) -> { System.out.print(s + " "); });
                    // *0* *2* *8* 

c'est tout. Si elle est différente de l'expression lambda que vous avez vue, essayez de rechercher des modèles abrégés.

Recommended Posts