introduction
Dans cet article, à l'aide d'exemples, nous explorerons les expressions lambda en Java, leur utilisation avec des interfaces fonctionnelles, des interfaces fonctionnelles paramétrées et des API Stream.
Des expressions Lambda ont été ajoutées dans Java 8. Leur objectif principal est d'améliorer la lisibilité et de réduire la quantité de code.
Mais avant de passer aux lambdas, nous devons comprendre les interfaces fonctionnelles.
Qu'est-ce qu'une interface fonctionnelle?
Si une interface en Java contient une et une seule méthode abstraite, elle est appelée fonctionnelle. Cette méthode unique détermine le but de l'interface.
Par exemple, l'interface Runnable du package java.lang est fonctionnelle car elle ne contient qu'une seule méthode run ().
Exemple 1: déclarer une interface fonctionnelle en java
import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
//
double getValue();
}
Dans l'exemple ci-dessus, l'interface MyInterface n'a qu'une seule méthode abstraite, getValue (). Donc, cette interface est fonctionnelle.
Ici, nous avons utilisé l'annotationInterface fonctionnelle, ce qui aide le compilateur à comprendre que l'interface est fonctionnelle. Par conséquent, il ne permet pas plus d'une méthode abstraite. Cependant, nous pouvons l'omettre.
Dans Java 7, les interfaces fonctionnelles étaient traitées comme des méthodes abstraites uniques (SAM). Les SAM étaient généralement implémentés à l'aide de classes anonymes.
Exemple 2: implémentation de SAM avec une classe anonyme en java
public class FunctionInterfaceTest {
public static void main(String[] args) {
//
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(" Runnable.")
}
}).start();
}
}
Résultat de l'exécution:
Runnable.
Dans cet exemple, nous acceptons une classe anonyme pour appeler la méthode. Cela a aidé à écrire des programmes avec moins de lignes de code en Java 7. Cependant, la syntaxe est restée assez complexe et lourde.
Java 8 a étendu les capacités de SAM pour aller plus loin. Comme nous le savons, l'interface fonctionnelle ne contient qu'une seule méthode, par conséquent, nous n'avons pas besoin de spécifier le nom de la méthode lorsque vous la passez en argument. C'est exactement ce que les expressions lambda nous permettent de faire.
Introduction aux expressions lambda
Les expressions Lambda sont essentiellement une classe ou une méthode anonyme. L'expression lambda ne s'exécute pas d'elle-même. Au lieu de cela, il est utilisé pour implémenter la méthode définie dans l'interface fonctionnelle.
Comment écrire une expression lambda en Java?
En Java, les expressions lambda ont la syntaxe suivante:
(parameter list) -> lambda body
Ici, nous avons utilisé un nouvel opérateur (->) - l'opérateur lambda. Peut-être que la syntaxe semble un peu délicate. Prenons quelques exemples.
Disons que nous avons une méthode comme celle-ci:
double getPiValue() {
return 3.1415;
}
Nous pouvons l'écrire en utilisant un lambda comme:
() -> 3.1415
Cette méthode n'a pas de paramètres. Par conséquent, le côté gauche de l'expression contient des parenthèses vides. Le côté droit est le corps de l'expression lambda, qui définit son action. Dans notre cas, la valeur de retour est 3,1415.
Types d'expressions lambda
En Java, le corps d'un lambda peut être de deux types.
1. Une ligne
() -> System.out.println("Lambdas are great");
2. Bloc (multiligne)
() -> {
double pi = 3.1415;
return pi;
};
Ce type permet à une expression lambda d'avoir plusieurs opérations en interne. Ces opérations doivent être placées entre accolades, suivies d'un point-virgule.
Remarque: les expressions lambda sur plusieurs lignes doivent toujours avoir une instruction de retour, contrairement aux expressions sur une seule ligne.
Exemple 3: Expression Lambda Écrivons
un programme Java qui renvoie Pi à l'aide d'une expression lambda.
Comme indiqué précédemment, une expression lambda ne s'exécute pas automatiquement. Au contraire, il forme une implémentation d'une méthode abstraite déclarée dans une interface fonctionnelle.
Et donc, tout d'abord, nous devons décrire l'interface fonctionnelle.
import java.lang.FunctionalInterface;
//
@FunctionalInterface
interface MyInterface{
//
double getPiValue();
}
public class Main {
public static void main( String[] args ) {
// MyInterface
MyInterface ref;
// -
ref = () -> 3.1415;
System.out.println("Value of Pi = " + ref.getPiValue());
}
}
Résultat de l'exécution:
Value of Pi = 3.1415
Dans l'exemple ci-dessus:
- Nous avons créé une interface fonctionnelle, MyInterface, qui contient une méthode abstraite, getPiValue ().
- Dans la classe Main, nous avons déclaré une référence à MyInterface. Notez que nous pouvons déclarer une référence d'interface, mais nous ne pouvons pas en créer un objet.
// MyInterface ref = new myInterface(); // MyInterface ref;
- Ensuite, nous avons attribué l'expression lambda au lien
ref = () -> 3.1415;
- Enfin, nous avons appelé la méthode getPiValue () en utilisant la référence d'interface.
System.out.println("Value of Pi = " + ref.getPiValue());
Expressions lambda avec paramètres
Jusqu'à présent, nous avons créé des expressions lambda sans aucun paramètre. Cependant, tout comme les méthodes, les lambdas peuvent avoir des paramètres.
(n) -> (n % 2) == 0
Dans cet exemple, la variable n entre parenthèses est le paramètre passé à l'expression lambda. Le corps lambda prend un paramètre et le vérifie pour la parité.
Exemple 4: utilisation d'une expression lambda avec des paramètres
@FunctionalInterface
interface MyInterface {
//
String reverse(String n);
}
public class Main {
public static void main( String[] args ) {
// MyInterface
// -
MyInterface ref = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
//
System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
}
}
Résultat de l'exécution:
Lambda reversed = adbmaL
Interface fonctionnelle paramétrée
Jusqu'à présent, nous avons utilisé des interfaces fonctionnelles qui n'acceptent qu'un seul type de valeur. Par exemple:
@FunctionalInterface
interface MyInterface {
String reverseString(String n);
}
L'interface fonctionnelle ci-dessus accepte uniquement String et renvoie String. Cependant, nous pouvons rendre notre interface générique à utiliser avec n'importe quel type de données.
Exemple 5: Interface paramétrée et expressions Lambda
//
@FunctionalInterface
interface GenericInterface<T> {
//
T func(T t);
}
public class Main {
public static void main( String[] args ) {
//
// String
//
GenericInterface<String> reverse = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
System.out.println("Lambda reversed = " + reverse.func("Lambda"));
//
// Integer
//
GenericInterface<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1; i <= n; i++)
result = i * result;
return result;
};
System.out.println("factorial of 5 = " + factorial.func(5));
}
}
Résultat de l'exécution:
Lambda reversed = adbmaL
factorial of 5 = 120
Dans cet exemple, nous avons créé une interface fonctionnelle paramétrée GenericInterface qui contient une méthode func () paramétrée.
Ensuite, à l'intérieur de la classe Main:
- GenericInterface <String> reverse - crée un lien vers une interface qui fonctionne avec String.
- GenericInterface <Integer> factorial - Crée un lien vers une interface qui fonctionne avec Integer.
Expressions Lambda et API de flux
Le JDK8 ajoute un nouveau package, java.util.stream, qui permet aux développeurs Java d'effectuer des opérations telles que la recherche, le filtrage, la mise en correspondance, la fusion ou la manipulation de collections telles que les listes.
Par exemple, nous avons un flux de données (dans notre cas, une liste de chaînes), où chaque chaîne contient le nom d'un pays et sa ville. Nous pouvons désormais traiter ce flux de données et sélectionner uniquement les villes du Népal.
Pour cela, nous pouvons utiliser une combinaison d'API Stream et d'expressions lambda.
Exemple 6: Utilisation de Lambdas dans l'API Stream
import java.util.ArrayList;
import java.util.List;
public class StreamMain {
//
static List<String> places = new ArrayList<>();
//
public static List getPlaces(){
//
places.add("Nepal, Kathmandu");
places.add("Nepal, Pokhara");
places.add("India, Delhi");
places.add("USA, New York");
places.add("Africa, Nigeria");
return places;
}
public static void main( String[] args ) {
List<String> myPlaces = getPlaces();
System.out.println("Places from Nepal:");
//
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
}
}
Résultat de l'exécution:
Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA
Dans l'exemple ci-dessus, notez cette expression:
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
Ici, nous utilisons des méthodes comme filter (), map (), forEach () de l'API Stream, qui peuvent prendre des lambdas comme paramètre.
De plus, nous pouvons décrire nos propres expressions en nous basant sur la syntaxe décrite ci-dessus. Cela nous permettra de réduire le nombre de lignes de code.