Java Lambda-expressies: Van basis tot Stream API
Java Lambda-expressies onder de knie
Verkent Java's lambda-expressies, functionele interfaces, generics en de Stream API met duidelijke voorbeelden.
Java 8 introduceerde lambda-expressies om de expressieve kracht van de taal te vergroten, waardoor ontwikkelaars meer beknopte en leesbare code kunnen schrijven.
Voordat we in lambdas duiken, is het essentieel om functionele interfaces te begrijpen, de ruggengraat van lambda-implementatie.
Wat is een functionele interface?
Een functionele interface is een Java-interface die precies één abstracte methode bevat. Deze enkele methode definieert het beoogde doel van de interface.
De Runnable-interface uit java.lang is een klassiek voorbeeld, met alleen de run()-methode.
Voorbeeld 1: Een functionele interface definiëren
import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MijnInterface {
// Enkele abstracte methode
double getWaarde();
}
Hier is MijnInterface geannoteerd met @FunctionalInterface, wat de compiler dwingt het functionele interface-contract na te leven, wat per ongeluk toevoegen van meerdere abstracte methoden voorkomt. De annotatie is optioneel maar sterk aanbevolen voor duidelijkheid en veiligheid.
In Java 7 werden functionele interfaces aangeduid als Single Abstract Methods (SAMs) en typisch geïmplementeerd met anonieme klassen.
Voorbeeld 2: Een SAM implementeren met een anonieme klasse
public class FunctioneleInterfaceTest {
public static void main(String[] args) {
// Anonieme klasse die Runnable implementeert
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Ik heb de Runnable functionele interface geïmplementeerd.");
}
}).start();
}
}
Output:
Ik heb de Runnable functionele interface geïmplementeerd.
Hoewel anonieme klassen boilerplate verminderen ten opzichte van traditionele implementaties, blijft de syntax verbose. Lambda-expressies elimineren de noodzaak voor expliciete methodennamen bij het doorgeven van functionele interfaces als argumenten.
Inleiding tot Lambda-expressies
Een lambda-expressie is in essentie een anonieme methode die een beknopte implementatie levert van de abstracte methode van een functionele interface. Het voert niet onafhankelijk uit; het levert het gedrag voor de interface-methode.
Hoe een lambda-expressie in Java te definiëren
De algemene syntax is:
(parameterlijst) -> lambda-body
->) scheidt de parameterlijst van de body. Hieronder concrete voorbeelden.
Stel we hebben een methode die Pi retourneert:
double getPiWaarde() { return 3.1415; }
Met een lambda:
() -> 3.1415
Zonder parameters is de linkerkant van de pijl leeg, en de rechterkant is de expressie-body die de waarde retourneert.
Typen lambda-bodies
Java lambda-bodies komen in twee vormen:
1. Expressie-body – bevat één expressie.
() -> System.out.println("Lambdas zijn geweldig");
2. Blok-body – in accolades gekapseld, toestaat meerdere statements en een optionele return.
() -> { double pi = 3.1415; return pi; };
Let op: Expressie-bodies vereisen geen expliciete return, blok-bodies wel als een waarde geretourneerd wordt.
Voorbeeld 3: Lambda-expressie in de praktijk
Laten we een Java-programma schrijven dat Pi retourneert met een lambda-expressie.
import java.lang.FunctionalInterface;
@FunctionalInterface
interface MijnInterface {
double getPiWaarde();
}
public class Main {
public static void main(String[] args) {
MijnInterface ref = () -> 3.1415;
System.out.println("Waarde van Pi = " + ref.getPiWaarde());
}
}
Output:
Waarde van Pi = 3.1415
Belangrijkste punten:
MijnInterfacedefinieert één abstracte methode- Een referentie naar
MijnInterfacewordt gedeclareerd maar niet direct geïnstantieerd - De lambda-expressie levert de implementatie voor
getPiWaarde() - Het aanroepen van
ref.getPiWaarde()triggert de lambda-body
Lambda-expressies met parameters
Net als methoden kunnen lambdas parameters accepteren. Voorbeeld:
(n) -> (n % 2) == 0
Deze lambda checkt of het invoer-integer even is.
Voorbeeld 4: Een string omkeren met een geparametriseerde lambda
@FunctionalInterface
interface MijnInterface {
String omkeren(String s);
}
public class Main {
public static void main(String[] args) {
MijnInterface ref = (str) -> {
String resultaat = "";
for (int i = str.length() - 1; i >= 0; i--) {
resultaat += str.charAt(i);
}
return resultaat;
};
System.out.println("Lambda omgekeerd = " + ref.omkeren("Lambda"));
}
}
Output:
Lambda omgekeerd = adbmaL
Generieke functionele interface
Functionele interfaces kunnen generiek zijn, zodat ze op elk datatype kunnen opereren. Bijvoorbeeld:
@FunctionalInterface
interface MijnInterface<T> {
T func(T t);
}
Voorbeeld 5: Generieke lambdas voor Strings en Integers
public class Main {
public static void main(String[] args) {
// String omkeren
MijnInterface<String> omkeren = (str) -> {
String resultaat = "";
for (int i = str.length() - 1; i >= 0; i--) {
resultaat += str.charAt(i);
}
return resultaat;
};
System.out.println("Lambda omgekeerd = " + omkeren.func("Lambda"));
// Integer faculteit
MijnInterface<Integer> faculteit = (n) -> {
int resultaat = 1;
for (int i = 1; i <= n; i++) {
resultaat *= i;
}
return resultaat;
};
System.out.println("faculteit van 5 = " + faculteit.func(5));
}
}
Output:
Lambda omgekeerd = adbmaL faculteit van 5 = 120
Lambda-expressies en de Stream API
Het java.util.stream-pakket, geïntroduceerd in JDK 8, stelt ontwikkelaars in staat om functionele-stijl operaties op collecties uit te voeren: filteren, mappen, reduceren, sorteren, en meer.
Bekijk een lijst met plaatsnamen, elk voorzien van een land-prefix. Met streams en lambdas kun je efficiënt data extraheren en manipuleren.
Voorbeeld 6: Stream-verwerking met lambdas
import java.util.ArrayList;
import java.util.List;
public class StreamMain {
static List<String> plaatsen = new ArrayList<>();
public static List<String> getPlaatsen() {
plaatsen.add("Nepal, Kathmandu");
plaatsen.add("Nepal, Pokhara");
plaatsen.add("India, Delhi");
plaatsen.add("USA, New York");
plaatsen.add("Africa, Nigeria");
return plaatsen;
}
public static void main(String[] args) {
List<String> mijnPlaatsen = getPlaatsen();
System.out.println("Plaatsen uit Nepal:");
mijnPlaatsen.stream()
.filter(p -> p.startsWith("Nepal"))
.map(p -> p.toUpperCase())
.sorted()
.forEach(p -> System.out.println(p));
}
}
Output:
Plaatsen uit Nepal: NEPAL, KATHMANDU NEPAL, POKHARA
Hier accepteert de ketting van stream-operaties – filter, map, sorted, forEach – elk een lambda, wat boilerplate drastisch vermindert ten opzichte van pre-Java-8 oplossingen.
Java
- C#-interfaces onder de knie: definitie, implementatie en praktische use cases
- Java-operators beheersen: typen, syntax en praktische voorbeelden
- Java Expressies, Statements en Blokken begrijpen
- Java interfaces meesteren: concepten, implementatie en best practices
- Java Try-with-resources meesteren: automatische resource management uitgelegd
- Java Annotaties uitgelegd: typen, plaatsing en praktische voorbeelden
- Interface vs Abstracte Klasse in Java: De Juiste Abstractie Kiezen
- Java Reguliere Expressies: Patroonherkenning onder de knie krijgen met java.util.regex
- Java Interfaces begrijpen: Definities, Gebruik en Best Practices
- Java 9 Private Interface Methods: Encapsulatie & herbruik verbeteren
- Java 8 Lambda Expressies beheersen: Gids voor functioneel programmeren