Udfordringer i Java 8

1. Oversigt

Java 8 introducerede nogle nye funktioner, som mest drejede sig om brugen af ​​lambda-udtryk. I denne hurtige artikel vil vi se på ulemperne ved nogle af dem.

Og selvom dette ikke er en komplet liste, er det en subjektiv samling af de mest almindelige og populære klager vedrørende nye funktioner i Java 8.

2. Java 8 Stream og trådpool

Først og fremmest er Parallel Streams beregnet til at muliggøre let parallel behandling af sekvenser, og det fungerer ganske OK i enkle scenarier.

Stream bruger standard, fælles ForkJoinPool - deler sekvenser i mindre bidder og udfører operationer ved hjælp af flere tråde.

Der er dog en fangst. Der er ingen god måde at angiv hvilken ForkJoinPool at bruge og hvis en af ​​trådene sidder fast, bliver alle de andre ved hjælp af den delte pool nødt til at vente på, at de langvarige opgaver er færdige.

Heldigvis er der en løsning på det:

ForkJoinPool forkJoinPool = ny ForkJoinPool (2); forkJoinPool.submit (() -> / * noget parallel stream pipeline * /) .get ();

Dette vil skabe en ny, separat ForkJoinPool og alle opgaver, der genereres af den parallelle strøm, bruger den specificerede pool og ikke i den delte, standardopgave.

Det er værd at bemærke, at der er en anden potentiel fangst: “Denne teknik til at indsende en opgave til en gaffel-join-pool, at køre den parallelle strøm i denne pool er et implementerings 'trick' og fungerer ikke garanteret"ifølge Stuart Marks - Java og OpenJDK-udvikler fra Oracle. En vigtig nuance at huske på, når du bruger denne teknik.

3. Nedsat fejlfinding

Den nye kodestil forenkler alligevel vores kildekodekan forårsage hovedpine under fejlretning.

Lad os først se på dette enkle eksempel:

offentlig statisk int getLength (String input) {if (StringUtils.isEmpty (input) {throw new IllegalArgumentException ();} return input.length ();} List lengths = new ArrayList (); for (String name: Arrays.asList ( args)) {lengths.add (getLength (name));}

Dette er en standard imperativ Java-kode, der er selvforklarende.

Hvis vi passerer tomme Snor som input - som et resultat - koden kaster en undtagelse, og i fejlfindingskonsol kan vi se:

på LmbdaMain.getLength (LmbdaMain.java:19) på LmbdaMain.main (LmbdaMain.java:34)

Lad os nu omskrive den samme kode ved hjælp af Stream API og se, hvad der sker, når en tom Snor bliver bestået:

Streamlængder = names.stream () .map (navn -> getLength (navn));

Opkaldsstakken vil se ud som:

på LmbdaMain.getLength (LmbdaMain.java:19) på LmbdaMain.lambda $ 0 (LmbdaMain.java:37) hos LmbdaMain $$ Lambda $ 1 / 821270929.apply (Ukendt kilde) på java.util.stream.ReferencePipeline $ 3 $ 1.accept ( ReferencePipeline.java:193) på java.util.Spliterators $ ArraySpliterator.forEachRemaining (Spliterators.java:948) på java.util.stream.AbstractPipeline.copyInto (AbstractPipeline.java:512) ved java.util.stream.AbstractPipeline.wrapondCopy (AbstractPipeline.java:502) på java.util.stream.ReduceOps $ ReduceOp.evaluateSequential (ReduceOps.java:708) ved java.util.stream.AbstractPipeline.evaluate (AbstractPipeline.java:234) ved java.util.stream. LongPipeline.reduce (LongPipeline.java:438) at java.util.stream.LongPipeline.sum (LongPipeline.java:396) at java.util.stream.ReferencePipeline.count (ReferencePipeline.java:526) at LmbdaMain.main (LmbdaMain .java: 39)

Det er den pris, vi betaler for at udnytte flere abstraktionslag i vores kode. IDE'er har imidlertid allerede udviklet solide værktøjer til fejlfinding af Java-streams.

4. Metoder, der vender tilbage Nul eller Valgfri

Valgfri blev introduceret i Java 8 for at give en typesikker måde at udtrykke optionalitet på.

Valgfri, angiver eksplicit, at returværdien muligvis ikke er til stede. Derfor kan kald af en metode returnere en værdi, og Valgfri bruges til at pakke den værdi ind - hvilket viste sig at være praktisk.

Desværre, på grund af Java bagudkompatibilitet, endte vi undertiden med Java API'er, der blandede to forskellige konventioner. I samme klasse kan vi finde metoder, der returnerer nul såvel som metoder, der returnerer Ekstraudstyr.

5. For mange funktionelle grænseflader

I java.util.function pakke, vi har en samling måltyper til lambda-udtryk. Vi kan skelne og gruppere dem som:

  • Forbruger - repræsenterer en operation, der tager nogle argumenter og ikke returnerer noget resultat
  • Fungere - repræsenterer en funktion, der tager nogle argumenter og producerer et resultat
  • Operatør - repræsenterer en operation på nogle type argumenter og returnerer et resultat af samme type som operanderne
  • Prædikat - repræsenterer et prædikat (boolsk-valueret funktion) af nogle argumenter
  • Leverandør - repræsenterer en leverandør, der ikke tager argumenter og returnerer resultater

Derudover har vi flere typer til at arbejde med primitiver:

  • IntConsumer
  • IntFunktion
  • IntPredicate
  • IntSupplier
  • IntToDoubleFunction
  • IntToLongFunction
  • ... og samme alternativer til Længes og Dobler

Desuden er specielle typer til funktioner med aritet 2:

  • BiConsumer
  • BiPredicate
  • BinaryOperator
  • BiFunction

Som et resultat indeholder hele pakken 44 funktionstyper, som helt sikkert kan begynde at være forvirrende.

6. Kontrollerede undtagelser og Lambda-udtryk

Kontrollerede undtagelser har allerede været et problematisk og kontroversielt problem før Java 8. Siden ankomsten af ​​Java 8 opstod det nye nummer.

Kontrollerede undtagelser skal enten fanges med det samme eller erklæres. Siden java.util.function funktionelle grænseflader erklærer ikke kasteundtagelser, kode, der kaster markeret undtagelse, mislykkes under kompilering:

statisk ugyldig writeToFile (heltal) kaster IOException {// logik til at skrive til fil, der kaster IOException}
Liste heltal = Arrays.asList (3, 9, 7, 0, 10, 20); heltal.forEach (i -> writeToFile (i));

En måde at løse dette problem på er at indpakke den kontrollerede undtagelse i en prøve-fangst blokere og genkaste RuntimeException:

Liste heltal = Arrays.asList (3, 9, 7, 0, 10, 20); integers.forEach (i -> {try {writeToFile (i);} catch (IOException e) {throw new RuntimeException (e);}});

Dette fungerer. Men at kaste RuntimeException modsiger formålet med kontrolleret undtagelse og gør hele koden pakket med kedelpladekode, som vi forsøger at reducere ved at udnytte lambda-udtryk. En af de hackede løsninger er at stole på sneaky-throw-hacket.

En anden løsning er at skrive et forbrugerfunktionelt interface - der kan give en undtagelse:

@FunctionalInterface offentlig grænseflade ThrowingConsumer {ugyldig accept (T t) kaster E; }
statisk Consumer throwingConsumerWrapper (ThrowingConsumer throwingConsumer) {return i -> {try {throwingConsumer.accept (i); } fange (Undtagelse ex) {kast ny RuntimeException (ex); }}; }

Desværre indpakker vi stadig den afkrydsede undtagelse i en runtime-undtagelse.

Endelig kan vi, for at få en dybdegående løsning og forklare problemet, udforske følgende dybdykning: Undtagelser i Java 8 Lambda Expressions.

8. Konklusion

I denne hurtige opskrivning diskuterede vi nogle af ulemperne ved Java 8.

Mens nogle af dem var bevidste designvalg foretaget af Java-sprogarkitekter, og i mange tilfælde er der en løsning eller alternativ løsning; vi skal være opmærksomme på deres mulige problemer og begrænsninger.