Welkom bij de Java 8 functionele interfaces voorbeeld tutorial. Java is altijd een Object Georiënteerde Programmeertaal geweest. Dit betekent dat alles in de Java-programmering draait om Objecten (behalve enkele primitieve types voor eenvoud). We hebben niet alleen functies in Java, ze maken deel uit van een Klasse en we moeten de klasse/object gebruiken om een functie aan te roepen.
Java 8 Functionele Interfaces
Als we naar andere programmeertalen zoals C++, JavaScript kijken, worden ze functionele programmeertalen genoemd omdat we functies kunnen schrijven en ze kunnen gebruiken wanneer dat nodig is. Sommige van deze talen ondersteunen zowel Object Georiënteerde Programmering als Functionele Programmering. Object georiënteerd zijn is niet slecht, maar het brengt veel redundantie naar het programma. Bijvoorbeeld, laten we zeggen dat we een instantie van Runnable moeten maken. Normaal doen we dat met behulp van anonieme klassen zoals hieronder.
Runnable r = new Runnable(){
@Override
public void run() {
System.out.println("My Runnable");
}};
Als je naar de bovenstaande code kijkt, is het werkelijke gedeelte dat van belang is de code binnen de run() methode. De rest van de code is vanwege de manier waarop Java-programma’s zijn gestructureerd. Java 8 Functionele Interfaces en Lambda Expressies helpen ons bij het schrijven van kleinere en schonere code door veel boilerplate-code te verwijderen.
Java 8 Functioneel Interface
Een interface met precies één abstracte methode wordt een Functioneel Interface genoemd. De annotatie @FunctionalInterface
wordt toegevoegd zodat we een interface als functioneel kunnen markeren. Het is niet verplicht om het te gebruiken, maar het is een best practice om het te gebruiken bij functionele interfaces om per ongeluk extra methoden toe te voegen. Als de interface is geannoteerd met @FunctionalInterface
en we proberen meer dan één abstracte methode te hebben, dan geeft het een compilerfout. Het belangrijkste voordeel van Java 8 functionele interfaces is dat we lambda-expressies kunnen gebruiken om ze te instantiëren en het gebruik van omvangrijke anonieme klassenimplementatie te vermijden. De Java 8 Collections API is herschreven en er is een nieuwe Stream API geïntroduceerd die veel functionele interfaces gebruikt. Java 8 heeft veel functionele interfaces gedefinieerd in het java.util.function
-pakket. Enkele van de nuttige Java 8 functionele interfaces zijn Consumer
, Supplier
, Function
en Predicate
. U kunt meer details over hen vinden in Java 8 Stream Voorbeeld. java.lang.Runnable
is een goed voorbeeld van een functionele interface met één abstracte methode run()
. Onderstaande codefragment biedt enige begeleiding voor functionele interfaces:
interface Foo { boolean equals(Object obj); }
// Niet functioneel omdat equals al een impliciet lid is (Objectklasse)
interface Comparator {
boolean equals(Object obj);
int compare(T o1, T o2);
}
// Functioneel omdat Comparator slechts één abstracte non-Object methode heeft
interface Foo {
int m();
Object clone();
}
// Niet functioneel omdat methode Object.clone niet openbaar is
interface X { int m(Iterable arg); }
interface Y { int m(Iterable arg); }
interface Z extends X, Y {}
// Functioneel: twee methoden, maar ze hebben dezelfde signatuur
interface X { Iterable m(Iterable arg); }
interface Y { Iterable m(Iterable arg); }
interface Z extends X, Y {}
// Functioneel: Y.m is een subsignatuur & return-type-vervangbaar
interface X { int m(Iterable arg); }
interface Y { int m(Iterable arg); }
interface Z extends X, Y {}
// Niet functioneel: Geen enkele methode heeft een subsignatuur van alle abstracte methoden
interface X { int m(Iterable arg, Class c); }
interface Y { int m(Iterable arg, Class > c); }
interface Z extends X, Y {}
// Niet functioneel: Geen enkele methode heeft een subsignatuur van alle abstracte methoden
interface X { long m(); }
interface Y { int m(); }
interface Z extends X, Y {}
// Compilerfout: geen enkele methode is return type-vervangbaar
interface Foo { void m(T arg); }
interface Bar { void m(T arg); }
interface FooBar extends Foo, Bar {}
// Compilerfout: verschillende signatureren, zelfde uitwissing
Lambda-Expressie
Lambda-Expressies zijn de manier waarop we functioneel programmeren kunnen visualiseren in de java object georiënteerde wereld. Objecten zijn de basis van de java-programmeertaal en we kunnen nooit een functie hebben zonder een Object, daarom biedt de Java-taal ondersteuning voor het gebruik van lambda-expressies alleen met functionele interfaces. Aangezien er slechts één abstracte functie is in de functionele interfaces, is er geen verwarring bij het toepassen van de lambda-expressie op de methode. De syntaxis van lambda-expressies is (argument) -> (body). Laten we nu eens kijken hoe we bovenstaande anonieme Runnable kunnen schrijven met behulp van lambda-expressie.
Runnable r1 = () -> System.out.println("My Runnable");
Laten we proberen te begrijpen wat er gebeurt in de lambda-expressie hierboven.
- `Runnable` is een functionele interface, daarom kunnen we een lambda-expressie gebruiken om er een instantie van te maken.
- Aangezien de `run()`-methode geen argumenten vereist, heeft onze lambda-expressie ook geen argumenten.
- Net als bij if-else blokken kunnen we accolades ({}) vermijden omdat we een enkele verklaring in de methodebody hebben. Voor meerdere verklaringen zouden we accolades moeten gebruiken zoals bij andere methoden.
Waarom hebben we Lambda-expressie nodig
-
Verminderde regels code Een van de duidelijke voordelen van het gebruik van een lambda-expressie is dat de hoeveelheid code wordt verminderd, we hebben al gezien hoe gemakkelijk we een instantie van een functionele interface kunnen maken met behulp van een lambda-expressie in plaats van een anonieme klasse.
-
Ondersteuning voor Sequentiële en Parallelle Uitvoering Een ander voordeel van het gebruik van lambda-expressies is dat we kunnen profiteren van de ondersteuning voor sequentiële en parallelle bewerkingen van de Stream API. Om dit uit te leggen, laten we een eenvoudig voorbeeld nemen waarbij we een methode moeten schrijven om te testen of een doorgegeven getal een priemgetal is of niet. Traditioneel zouden we de code als volgt schrijven. De code is niet volledig geoptimaliseerd maar goed voor het voorbeeld, dus heb even geduld.
//Traditionele aanpak private static boolean isPrime(int number) { if(number < 2) return false; for(int i=2; i<number; i++){ if(number % i == 0) return false; } return true; }
Het probleem met bovenstaande code is dat deze sequentieel van aard is; als het getal erg groot is, zal het aanzienlijke tijd in beslag nemen. Een ander probleem met de code is dat er zoveel uitstappunten zijn en dat deze niet leesbaar is. Laten we eens kijken hoe we dezelfde methode kunnen schrijven met behulp van lambda-expressies en de stream-API.
//Declaratieve aanpak private static boolean isPrime(int number) { return number > 1 && IntStream.range(2, number).noneMatch( index -> number % index == 0); }
IntStream
is een reeks van primitieve int-waarden die sequentiële en parallelle aggregatiebewerkingen ondersteunen. Dit is de int-primitieve specialisatie vanStream
. Voor meer leesbaarheid kunnen we de methode ook als volgt schrijven.private static boolean isPrime(int number) { IntPredicate isDivisible = index -> number % index == 0; return number > 1 && IntStream.range(2, number).noneMatch( isDivisible); }
Als u niet bekend bent met IntStream, de range()-methode ervan retourneert een sequentiële geordende IntStream van startInclusive (inclusief) tot endExclusive (exclusief) met een incrementele stap van 1. De noneMatch()-methode retourneert of er geen elementen in deze stream overeenkomen met de opgegeven predicaat. Het kan het predicaat op alle elementen vermijden als dit niet nodig is om het resultaat te bepalen.
-
Gedrag doorgeven aan methoden Laten we eens kijken hoe we lambda-expressies kunnen gebruiken om het gedrag van een methode door te geven aan de hand van een eenvoudig voorbeeld. Laten we zeggen dat we een methode moeten schrijven om de getallen in een lijst op te tellen als ze aan een bepaald criterium voldoen. We kunnen Predicate gebruiken en een methode schrijven zoals hieronder.
public static int somMetVoorwaarde(List<Integer> getallen, Predicate<Integer> voorwaarde) { return getallen.parallelStream() .filter(voorwaarde) .mapToInt(i -> i) .sum(); }
Voorbeeldgebruik:
//som van alle getallen somMetVoorwaarde(getallen, n -> true) //som van alle even getallen somMetVoorwaarde(getallen, i -> i%2==0) //som van alle getallen groter dan 5 somMetVoorwaarde(getallen, i -> i>5)
-
Hogere Efficiëntie met luiheid Nog een voordeel van het gebruik van lambda-expressies is de luie evaluatie. Laten we bijvoorbeeld zeggen dat we een methode moeten schrijven om het maximale oneven getal in het bereik van 3 tot 11 te vinden en het kwadraat ervan terug te geven. Normaal gesproken zouden we code voor deze methode schrijven zoals hieronder:
private static int findSquareOfMaxOdd(List<Integer> numbers) { int max = 0; for (int i : numbers) { if (i % 2 != 0 && i > 3 && i < 11 && i > max) { max = i; } } return max * max; }
Het bovenstaande programma zal altijd in volgorde worden uitgevoerd, maar we kunnen Stream API gebruiken om dit te bereiken en te profiteren van luiheid. Laten we eens kijken hoe we deze code op functionele programmeerwijze kunnen herschrijven met behulp van Stream API en lambda-expressies.
public static int findSquareOfMaxOdd(List<Integer> numbers) { return numbers.stream() .filter(NumberTest::isOdd) //Predicate is een functionele interface en .filter(NumberTest::isGreaterThan3) // we gebruiken lambda's om deze te initialiseren .filter(NumberTest::isLessThan11) // in plaats van anonieme binnenklassen .max(Comparator.naturalOrder()) .map(i -> i * i) .get(); } public static boolean isOdd(int i) { return i % 2 != 0; } public static boolean isGreaterThan3(int i){ return i > 3; } public static boolean isLessThan11(int i){ return i < 11; }
Als je verrast bent door de dubbele dubbele punt (::) operator, deze is geïntroduceerd in Java 8 en wordt gebruikt voor methodereferenties. De Java-compiler zorgt voor het koppelen van de argumenten aan de opgeroepen methode. Het is een verkorte vorm van lambda-expressies
i -> isGreaterThan3(i)
ofi -> NumberTest.isGreaterThan3(i)
.
Voorbeelden van Lambda Expressies
Hieronder geef ik enkele codefragmenten voor lambda-expressies met korte opmerkingen die ze uitleggen.
() -> {} // No parameters; void result
() -> 42 // No parameters, expression body
() -> null // No parameters, expression body
() -> { return 42; } // No parameters, block body with return
() -> { System.gc(); } // No parameters, void block body
// Complex blok lichaam met meerdere retourneringen
() -> {
if (true) return 10;
else {
int result = 15;
for (int i = 1; i < 10; i++)
result *= i;
return result;
}
}
(int x) -> x+1 // Single declared-type argument
(int x) -> { return x+1; } // same as above
(x) -> x+1 // Single inferred-type argument, same as below
x -> x+1 // Parenthesis optional for single inferred-type case
(String s) -> s.length() // Single declared-type argument
(Thread t) -> { t.start(); } // Single declared-type argument
s -> s.length() // Single inferred-type argument
t -> { t.start(); } // Single inferred-type argument
(int x, int y) -> x+y // Multiple declared-type parameters
(x,y) -> x+y // Multiple inferred-type parameters
(x, final y) -> x+y // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y // Illegal: can't mix inferred and declared types
Methode- en Constructoreferenties
A method reference is used to refer to a method without invoking it; a constructor reference is similarly used to refer to a constructor without creating a new instance of the named class or array type. Examples of method and constructor references:
System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new
Dat is alles voor de Java 8 Functionele Interfaces en Lambda Expressie Tutorial. Ik zou sterk aanraden om er naar te kijken omdat deze syntax nieuw is voor Java en het enige tijd zal kosten om het te begrijpen. Je zou ook Java 8 Functies moeten bekijken om meer te leren over alle verbeteringen en veranderingen in de Java 8 release.
Source:
https://www.digitalocean.com/community/tutorials/java-8-functional-interfaces