Uitdagingen in Java 8

1. Overzicht

Java 8 introduceerde enkele nieuwe functies, die voornamelijk draaiden om het gebruik van lambda-expressies. In dit korte artikel gaan we de nadelen van enkele van hen bekijken.

En hoewel dit geen volledige lijst is, is het een subjectieve verzameling van de meest voorkomende en populaire klachten over nieuwe functies in Java 8.

2. Java 8 Stream en Thread Pool

Allereerst zijn Parallel Streams bedoeld om gemakkelijke parallelle verwerking van reeksen mogelijk te maken, en dat werkt redelijk goed voor eenvoudige scenario's.

De Stream gebruikt de standaard, common ForkJoinPool - splitst reeksen in kleinere brokken en voert bewerkingen uit met behulp van meerdere threads.

Er is echter een addertje onder het gras. Er is geen goede manier om specificeer welke ForkJoinPool gebruiken en daarom, als een van de threads vastloopt, zullen alle andere, die de gedeelde pool gebruiken, moeten wachten tot de langlopende taken zijn voltooid.

Gelukkig is daar een oplossing voor:

ForkJoinPool forkJoinPool = nieuwe ForkJoinPool (2); forkJoinPool.submit (() -> / * een parallelle stroompijplijn * /) .get ();

Hierdoor wordt een nieuw, apart ForkJoinPool en alle taken die door de parallelle stream worden gegenereerd, zullen de gespecificeerde pool gebruiken en niet in de gedeelde, standaardpool.

Het is vermeldenswaard dat er nog een andere mogelijke vangst is: "Deze techniek van het indienen van een taak bij een fork-join-pool, om de parallelle stream in die pool uit te voeren, is een implementatietruc en werkt niet gegarandeerd", volgens Stuart Marks - Java- en OpenJDK-ontwikkelaar van Oracle. Een belangrijke nuance om in gedachten te houden bij het gebruik van deze techniek.

3. Verminderde foutopsporing

De nieuwe coderingsstijl vereenvoudigt onze broncode tochkan hoofdpijn veroorzaken tijdens het debuggen ervan.

Laten we eerst eens kijken naar dit eenvoudige voorbeeld:

public static int getLength (String input) {if (StringUtils.isEmpty (input) {throw new IllegalArgumentException ();} return input.length ();} Lijstlengtes = nieuwe ArrayList (); voor (String naam: Arrays.asList ( args)) {lengths.add (getLength (naam));}

Dit is een standaard verplichte Java-code die voor zichzelf spreekt.

Als we leeg passeren Draad als invoer - als resultaat - zal de code een uitzondering genereren, en in de debug-console kunnen we zien:

bij LmbdaMain.getLength (LmbdaMain.java:19) bij LmbdaMain.main (LmbdaMain.java:34)

Laten we nu dezelfde code herschrijven met Stream API en kijken wat er gebeurt als een leeg Draad wordt gepasseerd:

Stream lengtes = names.stream () .map (naam -> getLength (naam));

De call-stack ziet er als volgt uit:

bij LmbdaMain.getLength (LmbdaMain.java:19) bij LmbdaMain.lambda $ 0 (LmbdaMain.java:37) bij LmbdaMain $$ Lambda $ 1 / 821270929.apply (onbekende bron) bij java.util.stream.ReferencePipeline $ 3 $ 1 accepteren ( ReferencePipeline.java:193) bij java.util.Spliterators $ ArraySpliterator.forEachRemaining (Spliterators.java:948) bij java.util.stream.AbstractPipeline.copyInto (AbstractPipeline.java:512) bij java.util.stream.AbstractPipeline.wrapAndCopyInto (AbstractPipeline.java:502) bij java.util.stream.ReduceOps $ ReduceOp.evaluateSequential (ReduceOps.java:708) bij java.util.stream.AbstractPipeline.evaluate (AbstractPipeline.java:234) bij java.util.stream. LongPipeline.reduce (LongPipeline.java:438) bij java.util.stream.LongPipeline.sum (LongPipeline.java:396) bij java.util.stream.ReferencePipeline.count (ReferencePipeline.java:526) bij LmbdaMain.main (LmbdaMain .java: 39)

Dat is de prijs die we betalen voor het gebruik van meerdere abstractielagen in onze code. IDE's hebben echter al solide tools ontwikkeld voor het debuggen van Java-streams.

4. Methoden om terug te keren Nul of Optioneel

Optioneel werd geïntroduceerd in Java 8 om een ​​type-veilige manier te bieden om optionaliteit uit te drukken.

Optioneel, geeft expliciet aan dat de geretourneerde waarde mogelijk niet aanwezig is. Daarom kan het aanroepen van een methode een waarde retourneren, en Optioneel wordt gebruikt om die waarde erin te verpakken - wat handig bleek te zijn.

Helaas, vanwege de achterwaartse compatibiliteit met Java, eindigden we soms met Java-API's die twee verschillende conventies door elkaar mengden. In dezelfde klasse kunnen we methoden vinden die null-waarden retourneren, evenals methoden die terugkeren Optioneel.

5. Te veel functionele interfaces

In de java.util.function pakket, hebben we een verzameling doeltypes voor lambda-expressies. We kunnen ze onderscheiden en groeperen als:

  • Klant - vertegenwoordigt een bewerking waarvoor enkele argumenten nodig zijn en geen resultaat retourneert
  • Functie - staat voor een functie waaraan een aantal argumenten moet doorgegeven worden en die een resultaat oplevert
  • Operator - staat voor een bewerking op een bepaald type argumenten en retourneert een resultaat van hetzelfde type als de operanden
  • Predikaat - vertegenwoordigt een predikaat (boolean-waarde functie) van enkele argumenten
  • Leverancier - staat voor een leverancier die geen argumenten accepteert en resultaten retourneert

Daarnaast hebben we extra typen om met primitieven te werken:

  • IntConsumer
  • IntFunctie
  • IntPrediceren
  • IntSupplier
  • IntToDoubleFunction
  • IntToLongFunction
  • … En dezelfde alternatieven voor Longs en Dubbel

Verder speciale types voor functies met de ariteit van 2:

  • BiConsumer
  • BiPredicate
  • BinaryOperator
  • BiFunction

Als gevolg hiervan bevat het hele pakket 44 functionele typen, wat zeker verwarrend kan worden.

6. Gecontroleerde uitzonderingen en Lambda-expressies

Gecontroleerde uitzonderingen waren al een problematisch en controversieel probleem vóór Java 8. Sinds de komst van Java 8 is het nieuwe probleem ontstaan.

Gecontroleerde uitzonderingen moeten ofwel onmiddellijk worden opgevangen ofwel worden aangegeven. Sinds java.util.function functionele interfaces declareren geen throwing-uitzonderingen, code die een gecontroleerde uitzondering genereert, zal mislukken tijdens het compileren:

static void writeToFile (Integer integer) gooit IOException {// logica om naar een bestand te schrijven dat IOException genereert}
Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); gehele getallen.forEach (i -> writeToFile (i));

Een manier om dit probleem op te lossen, is door de aangevinkte uitzondering in een proberen te vangen blokkeren en opnieuw gooien RuntimeException:

Lijst gehele getallen = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {probeer {writeToFile (i);} catch (IOException e) {throw nieuwe RuntimeException (e);}});

Dit zal lukken. Echter, gooien RuntimeException is in tegenspraak met het doel van een gecontroleerde uitzondering en zorgt ervoor dat de hele code wordt omhuld met standaardcode, die we proberen te verminderen door gebruik te maken van lambda-expressies. Een van de hacky-oplossingen is om te vertrouwen op de sneaky-throw-hack.

Een andere oplossing is om een ​​functionele consumenteninterface te schrijven - dat kan een uitzondering opleveren:

@FunctionalInterface openbare interface ThrowingConsumer {void accept (T t) gooit E; }
statische Consumer throwingConsumerWrapper (ThrowingConsumer throwingConsumer) {return i -> {probeer {throwingConsumer.accept (i); } catch (Exception ex) {throw new RuntimeException (ex); }}; }

Helaas verpakken we de gecontroleerde uitzondering nog steeds in een runtime-uitzondering.

Tot slot, voor een diepgaande oplossing en uitleg van het probleem, kunnen we de volgende diepgaande duik onderzoeken: Uitzonderingen in Java 8 Lambda Expressions.

8. Conclusie

In dit korte artikel hebben we enkele van de nadelen van Java 8 besproken.

Terwijl sommige van hen opzettelijke ontwerpkeuzes waren gemaakt door Java-taalarchitecten en in veel gevallen is er een tijdelijke oplossing of een alternatieve oplossing; we moeten ons bewust zijn van hun mogelijke problemen en beperkingen.