Functioneel programmeren in Java

1. Inleiding

In deze tutorial zullen we de kernprincipes van het functionele programmeerparadigma begrijpen en hoe we deze kunnen oefenen in de programmeertaal Java. We behandelen ook enkele van de geavanceerde functionele programmeertechnieken.

Dit stelt ons ook in staat om de voordelen te evalueren die we halen uit functioneel programmeren, vooral in Java.

2. Wat is functioneel programmeren?

Kortom, functioneel programmeren is een stijl van het schrijven van computerprogramma's die berekeningen behandelen als het evalueren van wiskundige functies. Dus, wat is een functie in de wiskunde?

Een functie is een uitdrukking die een input-set relateert aan een output-set.

Belangrijk is dat de uitvoer van een functie alleen afhangt van zijn invoer. Interessanter is dat we twee of meer functies samen kunnen samenstellen om een ​​nieuwe functie te krijgen.

2.1. Lambda Calculus

Om te begrijpen waarom deze definities en eigenschappen van wiskundige functies belangrijk zijn bij het programmeren, zullen we een beetje terug in de tijd moeten gaan. In de jaren dertig ontwikkelde de wiskundige Alonzo Chruch zich een formeel systeem om berekeningen uit te drukken op basis van functie-abstractie. Dit universele rekenmodel werd bekend als de Lambda Calculus.

Lambda-calculus had een enorme impact op de ontwikkeling van de theorie van programmeertalen, met name functionele programmeertalen. Doorgaans implementeren functionele programmeertalen lambda-calculus.

Omdat lambda-calculus zich richt op functiesamenstelling, bieden functionele programmeertalen expressieve manieren om software samen te stellen in functiesamenstelling.

2.2. Categorisering van programmeerparadigma's

Functioneel programmeren is natuurlijk niet de enige programmeerstijl in de praktijk. In grote lijnen kunnen programmeerstijlen worden onderverdeeld in imperatieve en declaratieve programmeerparadigma's:

De imperatieve benadering definieert een programma als een reeks instructies die de status van het programma wijzigen totdat het de eindtoestand bereikt. Procedureel programmeren is een soort dwingend programmeren waarbij we programma's construeren met behulp van procedures of subroutines. Een van de populaire programmeerparadigma's, bekend als objectgeoriënteerd programmeren (OOP), breidt procedurele programmeerconcepten uit.

In tegenstelling daarmee is de declaratieve benadering drukt de logica van een berekening uit zonder de controlestroom te beschrijven in termen van een reeks uitspraken. Simpel gezegd, de focus van de declaratieve benadering is om te definiëren wat het programma moet bereiken in plaats van hoe het dit zou moeten bereiken. Functioneel programmeren is een subset van de declaratieve programmeertalen.

Deze categorieën hebben nog meer subcategorieën en de taxonomie wordt behoorlijk complex, maar daar gaan we in deze tutorial niet op in.

2.3. Categorisering van programmeertalen

Elke poging om de programmeertalen van vandaag formeel te categoriseren is een academische inspanning op zich! We zullen echter proberen te begrijpen hoe programmeertalen zijn verdeeld op basis van hun ondersteuning voor functionele programmering voor onze doeleinden.

Pure functionele talen, zoals Haskell, laten alleen puur functionele programma's toe.

Andere talen staan ​​beide echter toe functionele en procedurele programma's en worden beschouwd als onzuivere functionele talen. Veel talen vallen in deze categorie, waaronder Scala, Kotlin en Java.

Het is belangrijk om te begrijpen dat de meeste populaire programmeertalen van tegenwoordig algemene talen zijn, en daarom hebben ze de neiging om meerdere programmeerparadigma's te ondersteunen.

3. Fundamentele principes en concepten

In dit gedeelte worden enkele basisprincipes van functioneel programmeren behandeld en hoe u deze in Java kunt toepassen. Houd er rekening mee dat veel functies die we zullen gebruiken niet altijd deel uitmaakten van Java, en dat is het ook raadzaam om Java 8 of hoger te gebruiken om functioneel programmeren effectief te oefenen.

3.1. Eersteklas en hogere functies

Een programmeertaal zou eersteklas functies hebben als het functies behandelt als eersteklas burgers. In feite betekent het dat functies mogen alle bewerkingen ondersteunen die doorgaans beschikbaar zijn voor andere entiteiten. Deze omvatten het toewijzen van functies aan variabelen, ze als argumenten doorgeven aan andere functies en ze retourneren als waarden van andere functies.

Deze eigenschap maakt het mogelijk om functies van hogere orde in functionele programmering te definiëren. Functies van hogere orde kunnen functies als argumenten ontvangen en als resultaat een functie retourneren. Dit maakt verder verschillende technieken in functioneel programmeren mogelijk, zoals functiesamenstelling en currying.

Traditioneel was het alleen mogelijk om functies in Java door te geven met behulp van constructies zoals functionele interfaces of anonieme innerlijke klassen. Functionele interfaces hebben precies één abstracte methode en worden ook wel Single Abstract Method (SAM) -interfaces genoemd.

Laten we zeggen dat we een aangepaste comparator moeten bieden aan Collections.sort methode:

Collections.sort (numbers, new Comparator () {@Override public int Compare (Integer n1, Integer n2) {return n1.compareTo (n2);}});

Zoals we kunnen zien, is dit een vervelende en uitgebreide techniek - zeker niet iets dat ontwikkelaars aanmoedigt om functioneel te programmeren. Gelukkig bracht Java 8 er veel nieuwe functies om het proces te vergemakkelijken, zoals lambda-expressies, methodeverwijzingen en voorgedefinieerde functionele interfaces.

Laten we eens kijken hoe een lambda-uitdrukking ons kan helpen bij dezelfde taak:

Collections.sort (numbers, (n1, n2) -> n1.compareTo (n2));

Absoluut, dit is beknopter en begrijpelijker. Houd er echter rekening mee dat, hoewel dit ons de indruk kan geven functies te gebruiken als eersteklas burgers op Java, dat niet het geval is.

Achter de syntactische suiker van lambda-expressies verpakt Java deze nog steeds in functionele interfaces. Vandaar, Java behandelt een lambda-uitdrukking als een Voorwerp, wat in feite de echte eersteklas burger op Java is.

3.2. Pure functies

De definitie van pure functie benadrukt dat een pure functie moet een waarde retourneren die alleen op de argumenten is gebaseerd en mag geen bijwerkingen hebben. Dit klinkt misschien nogal in strijd met alle best practices in Java.

Omdat Java een objectgeoriënteerde taal is, wordt inkapseling aanbevolen als een kernprogrammeerpraktijk. Het moedigt het verbergen van de interne toestand van een object aan en toont alleen de noodzakelijke methoden om het te openen en te wijzigen. Daarom zijn deze methoden niet strikt pure functies.

Inkapseling en andere objectgeoriënteerde principes zijn natuurlijk slechts aanbevelingen en niet bindend in Java. In feite zijn ontwikkelaars onlangs begonnen met het besef van de waarde van het definiëren van onveranderlijke toestanden en methoden zonder bijwerkingen.

Laten we zeggen dat we de som willen vinden van alle getallen die we zojuist hebben gesorteerd:

Integer sum (lijstnummers) {retournummers.stream (). Collect (Collectors.summingInt (Integer :: intValue)); }

Nu hangt deze methode alleen af ​​van de argumenten die het ontvangt, en is daarom deterministisch. Bovendien veroorzaakt het geen bijwerkingen.

Bijwerkingen kunnen van alles zijn, behalve het beoogde gedrag van de methode. Bijvoorbeeld, bijwerkingen kunnen zo simpel zijn als het bijwerken van een lokale of globale staat of opslaan in een database voordat een waarde wordt geretourneerd. Puristen beschouwen houtkap ook als een bijwerking, maar we hebben allemaal onze eigen grenzen te stellen!

We kunnen echter redeneren over hoe we omgaan met legitieme bijwerkingen. Het kan bijvoorbeeld nodig zijn om het resultaat om oprechte redenen in een database op te slaan. Welnu, er zijn technieken in functioneel programmeren om met bijwerkingen om te gaan met behoud van pure functies.

We zullen er enkele in latere secties bespreken.

3.3. Onveranderlijkheid

Onveranderlijkheid is een van de kernprincipes van functioneel programmeren, en dat is het ook verwijst naar de eigenschap dat een entiteit niet kan worden gewijzigd nadat deze is geïnstantieerd. Nu in een functionele programmeertaal, wordt dit ondersteund door ontwerp op taalniveau. Maar in Java moeten we onze eigen beslissing nemen om onveranderlijke datastructuren te creëren.

Houd er rekening mee dat Java zelf biedt verschillende ingebouwde onveranderlijke typen, bijvoorbeeld, Draad. Dit is voornamelijk om veiligheidsredenen, aangezien we intensief gebruik maken van Draad bij het laden van klassen en als sleutels in op hash gebaseerde datastructuren. Er zijn verschillende andere ingebouwde onveranderlijke typen, zoals primitieve wrappers en wiskundige typen.

Maar hoe zit het met de datastructuren die we in Java maken? Natuurlijk zijn ze niet standaard onveranderlijk, en we moeten een paar wijzigingen aanbrengen om onveranderlijkheid te bereiken. De gebruik van de laatste trefwoord is er een van, maar daar houdt het niet op:

openbare klasse ImmutableData {private final String someData; private finale AnotherImmutableData anotherImmutableData; openbare ImmutableData (laatste String someData, laatste AnotherImmutableData anotherImmutableData) {this.someData = someData; this.anotherImmutableData = anotherImmutableData; } public String getSomeData () {return someData; } openbare AnotherImmutableData getAnotherImmutableData () {return anotherImmutableData; }} openbare klasse AnotherImmutableData {privé definitief Geheel getal someOtherData; openbare AnotherImmutableData (laatste geheel getal someData) {this.someOtherData = someData; } openbaar geheel getal getSomeOtherData () {retourneer someOtherData; }}

Merk op dat we een paar regels ijverig in acht moeten nemen:

  • Alle velden van een onveranderlijke datastructuur moeten onveranderlijk zijn
  • Dit moet ook gelden voor alle geneste typen en verzamelingen (inclusief wat ze bevatten)
  • Er moeten indien nodig een of meer constructors zijn voor initialisatie
  • Er zouden alleen accessormethoden moeten zijn, mogelijk zonder bijwerkingen

Haar niet gemakkelijk om het elke keer helemaal goed te krijgen, vooral wanneer de datastructuren complex beginnen te worden. Verschillende externe bibliotheken kunnen het werken met onveranderlijke gegevens in Java echter gemakkelijker maken. Immutables en Project Lombok bieden bijvoorbeeld gebruiksklare frameworks voor het definiëren van onveranderlijke datastructuren in Java.

3.4. Referentiële transparantie

Referentiële transparantie is misschien een van de moeilijkere principes van functioneel programmeren om te begrijpen. Het concept is echter vrij eenvoudig. Wij noem een ​​uitdrukking referentieel transparant als het vervangen ervan door de overeenkomstige waarde geen invloed heeft op het gedrag van het programma.

Dit maakt een aantal krachtige technieken in functioneel programmeren mogelijk, zoals functies van hogere orde en luie evaluatie. Laten we een voorbeeld nemen om dit beter te begrijpen:

openbare klasse SimpleData {privé Logger-logger = Logger.getGlobal (); privé String-gegevens; openbare String getData () {logger.log (Level.INFO, "Gegevens ophalen opgeroepen voor SimpleData"); gegevens retourneren; } openbare SimpleData setData (String data) {logger.log (Level.INFO, "Set data opgeroepen voor SimpleData"); this.data = data; dit teruggeven; }}

Dit is een typische POJO-klasse in Java, maar we zijn benieuwd of dit referentiële transparantie biedt. Laten we de volgende uitspraken bekijken:

String data = nieuwe SimpleData (). SetData ("Baeldung"). GetData (); logger.log (Level.INFO, nieuwe SimpleData (). setData ("Baeldung"). getData ()); logger.log (Level.INFO, gegevens); logger.log (Level.INFO, "Baeldung");

De drie bellen naar logger zijn semantisch equivalent maar niet referentieel transparant. De eerste oproep is niet referentieel transparant omdat het een bijwerking heeft. Als we deze aanroep vervangen door zijn waarde zoals in de derde aanroep, missen we de logboeken.

De tweede oproep is ook niet referentieel transparant zoals SimpleData is veranderlijk. Een telefoontje naar data.setData ergens in het programma zou het moeilijk maken om het te vervangen door zijn waarde.

Dus eigenlijk, voor referentiële transparantie moeten onze functies puur en onveranderlijk zijn. Dit zijn de twee randvoorwaarden die we eerder al hebben besproken. Als interessant resultaat van referentiële transparantie produceren we contextvrije code. Met andere woorden, we kunnen ze in elke volgorde en context uitvoeren, wat leidt tot verschillende optimalisatiemogelijkheden.

4. Functionele programmeertechnieken

De functionele programmeerprincipes die we eerder bespraken, stellen ons in staat om verschillende technieken te gebruiken om te profiteren van functioneel programmeren. In dit gedeelte bespreken we enkele van deze populaire technieken en begrijpen we hoe we ze in Java kunnen implementeren.

4.1. Functie Samenstelling

Functie samenstelling verwijst naar het samenstellen van complexe functies door eenvoudigere functies te combineren. Dit wordt voornamelijk bereikt in Java met behulp van functionele interfaces, die in feite doeltypes zijn voor lambda-expressies en methodeverwijzingen.

Typisch, elke interface met een enkele abstracte methode kan als functionele interface dienen. Daarom kunnen we vrij gemakkelijk een functionele interface definiëren. Java 8 biedt ons echter standaard veel functionele interfaces voor verschillende gebruiksscenario's onder het pakket java.util.function.

Veel van deze functionele interfaces bieden ondersteuning voor functiesamenstelling in termen van standaard en statisch methoden. Laten we de Functie interface om dit beter te begrijpen. Functie is een eenvoudige en generieke functionele interface die één argument accepteert en een resultaat oplevert.

Het biedt ook twee standaardmethoden, componeren en en dan, wat ons zal helpen bij het samenstellen van functies:

Functielog = (waarde) -> Math.log (waarde); Functie sqrt = (waarde) -> Math.sqrt (waarde); Functie logThenSqrt = sqrt.compose (log); logger.log (Level.INFO, String.valueOf (logThenSqrt.apply (3.14))); // Uitvoer: 1,06 Functie sqrtThenLog = sqrt.andThen (logboek); logger.log (Level.INFO, String.valueOf (sqrtThenLog.apply (3.14))); // Uitgang: 0,57

Beide methoden stellen ons in staat om meerdere functies in één functie samen te stellen, maar bieden verschillende semantiek. Terwijl componeren past eerst de functie toe die in het argument is doorgegeven en vervolgens de functie waarop het wordt aangeroepen, en dan doet hetzelfde in omgekeerde volgorde.

Verschillende andere functionele interfaces hebben interessante methoden om te gebruiken in functiesamenstelling, zoals de standaardmethoden en, of, en ontkennen in de Predikaat koppel. Hoewel deze functionele interfaces een enkel argument accepteren, zijn er specialisaties met twee ariteiten, zoals BiFunction en BiPredicate.

4.2. Monaden

Veel van de functionele programmeerconcepten zijn afgeleid van de categorietheorie, dat wil zeggen een algemene theorie van functies in de wiskunde. Het presenteert verschillende concepten van categorieën, zoals functoren en natuurlijke transformaties. Voor ons is het alleen belangrijk om te weten dat dit de basis is van het gebruik van monaden bij functioneel programmeren.

Formeel gezien is een monade een abstractie die het mogelijk maakt om programma's generiek te structureren. Dus eigenlijk een monade stelt ons in staat om een ​​waarde te verpakken, een reeks transformaties toe te passen en de waarde terug te krijgen met alle toegepaste transformaties. Natuurlijk zijn er drie wetten die elke monade moet volgen - linker identiteit, rechter identiteit en associativiteit - maar we zullen niet op de details ingaan.

Op Java zijn er een paar monaden die we vrij vaak gebruiken, zoals Optioneel en Stroom:

Optioneel.van (2) .flatMap (f -> Optioneel.of (3) .flatMap (s -> Optioneel.of (f + s)))

Nu, waarom bellen we Optioneel een monade? Hier, Optioneel stelt ons in staat om een ​​waarde te verpakken met behulp van de methode van en pas een reeks transformaties toe. We passen de transformatie toe door nog een ingepakte waarde toe te voegen met behulp van de methode flatMap.

Als we willen, kunnen we dat laten zien Optioneel volgt de drie wetten van monaden. Critici zullen er echter snel op wijzen dat een Optioneel breekt onder bepaalde omstandigheden de monadenwetten. Maar voor de meeste praktische situaties zou het goed genoeg voor ons moeten zijn.

Als we de basisprincipes van monaden begrijpen, zullen we ons snel realiseren dat er veel andere voorbeelden in Java zijn, zoals Stroom en CompletableFuture. Ze helpen ons verschillende doelen te bereiken, maar ze hebben allemaal een standaardsamenstelling waarin contextmanipulatie of transformatie wordt afgehandeld.

Natuurlijk, we kunnen onze eigen typen monaden in Java definiëren om verschillende doelen te bereiken zoals log monade, rapport monade of audit monade. Weet je nog hoe we het hebben over het omgaan met bijwerkingen bij functioneel programmeren? Het lijkt erop dat de monade een van de functionele programmeertechnieken is om dat te bereiken.

4.3. Currying

Currying is een wiskundig techniek voor het converteren van een functie waaraan meerdere argumenten kunnen doorgegeven worden in een reeks functies waaraan één argument moet doorgegeven worden. Maar waarom hebben we ze nodig bij functioneel programmeren? Het geeft ons een krachtige compositietechniek waarbij we geen functie met al zijn argumenten hoeven aan te roepen.

Bovendien realiseert een curried-functie zijn effect pas als alle argumenten zijn ontvangen.

In puur functionele programmeertalen zoals Haskell wordt currying goed ondersteund. In feite zijn alle functies standaard curried. In Java is het echter niet zo eenvoudig:

Functie gewicht = massa -> zwaartekracht -> massa * zwaartekracht; Functie weightOnEarth = weight.apply (9.81); logger.log (Level.INFO, "Mijn gewicht op aarde:" + weightOnEarth.apply (60.0)); Functie weightOnMars = weight.apply (3.75); logger.log (Level.INFO, "Mijn gewicht op Mars:" + weightOnMars.apply (60.0));

Hier hebben we een functie gedefinieerd om ons gewicht op een planeet te berekenen. Hoewel onze massa hetzelfde blijft, varieert de zwaartekracht per planeet. Wij kan de functie gedeeltelijk toepassen door alleen de zwaartekracht door te geven om een ​​functie voor een specifieke planeet te definiëren. Bovendien kunnen we deze gedeeltelijk toegepaste functie doorgeven als een argument of retourwaarde voor willekeurige compositie.

Currying hangt af van de taal om twee fundamentele kenmerken te bieden: lambda-uitdrukkingen en sluitingen. Lambda-expressies zijn anonieme functies die ons helpen code als gegevens te behandelen. We hebben eerder gezien hoe we ze kunnen implementeren met behulp van functionele interfaces.

Nu kan een lambda-uitdrukking zijn lexicale reikwijdte sluiten, die we definiëren als zijn afsluiting. Laten we een voorbeeld bekijken:

privé statische functie weightOnEarth () {laatste dubbele zwaartekracht = 9,81; retourmassa -> massa * zwaartekracht; }

Merk op hoe de lambda-uitdrukking, die we in de bovenstaande methode retourneren, afhangt van de insluitende variabele, die we afsluiting noemen. In tegenstelling tot andere functionele programmeertalen, Java heeft een beperking dat de omsluitende scope definitief of effectief definitief moet zijn.

Als een interessant resultaat stelt currying ons ook in staat om een ​​functionele interface in Java met willekeurige ariteit te creëren.

4.4. Herhaling

Recursie is een andere krachtige techniek in functioneel programmeren stelt ons in staat een probleem op te splitsen in kleinere stukjes. Het belangrijkste voordeel van recursie is dat het ons helpt de bijwerkingen te elimineren, wat typerend is voor elke dwingende stijl-looping.

Laten we eens kijken hoe we de faculteit van een getal berekenen met behulp van recursie:

Geheel getal faculteit (geheel getal) {return (getal == 1)? 1: nummer * faculteit (nummer - 1); }

Hier noemen we dezelfde functie recursief totdat we het basisscenario bereiken en beginnen we ons resultaat te berekenen.Merk op dat we de recursieve aanroep doen voordat we het resultaat bij elke stap of in woorden aan het begin van de berekening berekenen. Vandaar, deze stijl van recursie wordt ook wel hoofdrecursie genoemd.

Een nadeel van dit type recursie is dat elke stap de status van alle voorgaande stappen moet behouden totdat we het basisscenario bereiken. Dit is niet echt een probleem voor kleine aantallen, maar het vasthouden van de status voor grote aantallen kan inefficiënt zijn.

Een oplossing is a iets andere implementatie van de recursie die bekend staat als staartrecursie. Hier zorgen we ervoor dat de recursieve aanroep de laatste aanroep is die een functie doet. Laten we eens kijken hoe we de bovenstaande functie kunnen herschrijven om staartrecursie te gebruiken:

Geheel getal faculteit (geheel getal, resultaat geheel getal) {return (getal == 1)? resultaat: faculteit (nummer - 1, resultaat * nummer); }

Let op het gebruik van een accumulator in de functie, waardoor het niet nodig is om de status bij elke stap van recursie vast te houden. Het echte voordeel van deze stijl is om compileroptimalisaties te gebruiken waarbij de compiler kan beslissen om het stack-frame van de huidige functie los te laten, een techniek die bekend staat als tail-call-eliminatie.

Hoewel veel talen zoals Scala tail-call-eliminatie ondersteunen, heeft Java hier nog steeds geen ondersteuning voor. Dit maakt deel uit van de achterstand voor Java en zal misschien in de een of andere vorm komen als onderdeel van grotere veranderingen die worden voorgesteld onder Project Loom.

5. Waarom is functioneel programmeren belangrijk?

Nadat we de tutorial tot nu toe hebben doorlopen, moeten we ons afvragen waarom we zoveel moeite willen doen. Voor iemand met een Java-achtergrond, de verschuiving dat functionele programmeervereisten niet triviaal zijn. Er zouden dus enkele veelbelovende voordelen moeten zijn voor het adopteren van functioneel programmeren in Java.

Het grootste voordeel van het toepassen van functioneel programmeren in elke taal, inclusief Java, is zuivere functies en onveranderlijke toestanden. Als we achteraf nadenken, zijn de meeste programmeeruitdagingen geworteld in de bijwerkingen en de veranderlijke toestand op de een of andere manier. Gewoon van ze afkomen maakt ons programma gemakkelijker te lezen, te redeneren, te testen en te onderhouden.

Declaratieve programmering, als zodanig, leidt tot zeer beknopte en leesbare programma's. Functioneel programmeren, een subset van declaratief programmeren, biedt verschillende constructies zoals functies van hogere orde, functiesamenstelling en functieketting. Denk aan de voordelen die Stream API in Java 8 heeft gebracht voor het omgaan met datamanipulaties.

Maar laat u niet verleiden om over te schakelen, tenzij u er helemaal klaar voor bent. Houd er rekening mee dat functioneel programmeren geen eenvoudig ontwerppatroon is dat we onmiddellijk kunnen gebruiken en waarvan we kunnen profiteren. Functioneel programmeren is meer een verandering in de manier waarop we redeneren over problemen en hun oplossingen en hoe het algoritme gestructureerd moet worden.

Dus voordat we functionele programmering gaan gebruiken, moeten we onszelf trainen om na te denken over onze programma's in termen van functies.

6. Is Java een geschikte match?

Hoewel het moeilijk is om functionele programmeervoordelen te ontkennen, kunnen we niet anders dan ons afvragen of Java hiervoor een geschikte keuze is. Historisch gezien Java is geëvolueerd als een programmeertaal voor algemene doeleinden die geschikter is voor objectgeoriënteerd programmeren. Zelfs het denken aan het gebruik van functioneel programmeren vóór Java 8 was vervelend! Maar de dingen zijn zeker veranderd na Java 8.

Het feit dat er zijn geen echte functietypen in Java druist in tegen de basisprincipes van functioneel programmeren. De functionele interfaces in de vermomming van lambda-expressies maken het grotendeels goed, althans syntactisch. Dan het feit dat typen in Java zijn inherent veranderlijk en we moeten zoveel standaardplaat schrijven om onveranderlijke typen te creëren, het helpt niet.

Van een functionele programmeertaal verwachten we andere dingen die in Java ontbreken of moeilijk zijn. Bijvoorbeeld, de standaard evaluatiestrategie voor argumenten in Java is gretig. Maar luie evaluatie is een efficiëntere en aanbevolen manier voor functioneel programmeren.

We kunnen nog steeds luie evalueren in Java met behulp van kortsluiting door operators en functionele interfaces, maar het is meer betrokken.

De lijst is zeker niet compleet en kan generieke ondersteuning met type-erasure, ontbrekende ondersteuning voor tail-call-optimalisatie en andere dingen bevatten. We krijgen echter een breed beeld. Java is zeker niet geschikt om een ​​programma vanuit het niets op te starten in functioneel programmeren.

Maar wat als we al een bestaand programma hebben dat in Java is geschreven, waarschijnlijk in objectgeoriënteerd programmeren? Niets weerhoudt ons ervan om enkele van de voordelen van functioneel programmeren te krijgen, vooral met Java 8.

Dit is waar de meeste voordelen van functioneel programmeren liggen voor een Java-ontwikkelaar. Een combinatie van objectgeoriënteerd programmeren met de voordelen van functioneel programmeren kan een lange weg gaan.

7. Conclusie

In deze tutorial hebben we de basisprincipes van functioneel programmeren doorgenomen. We hebben de fundamentele principes besproken en hoe we ze op Java kunnen toepassen. Verder hebben we enkele populaire technieken in functioneel programmeren besproken met voorbeelden in Java.

Ten slotte hebben we enkele van de voordelen van functioneel programmeren besproken en beantwoord of Java hiervoor geschikt is.

De broncode voor het artikel is beschikbaar op GitHub.