Introduktion til Project Amber

1. Hvad er projektgult

Project Amber er et aktuelt initiativ fra udviklerne af Java og OpenJDK, der sigter mod at levere nogle små, men vigtige ændringer til JDK for at gøre udviklingsprocessen pænere. Dette har været i gang siden 2017 og har allerede leveret nogle ændringer i Java 10 og 11, hvor andre er planlagt til inkludering i Java 12 og endnu flere kommer i fremtidige udgivelser.

Disse opdateringer er alle pakket i form af JEP'er - JDK Enhancement Proposal-ordningen.

2. Leverede opdateringer

Til dato har Project Amber med succes leveret nogle ændringer i aktuelt frigivne versioner af JDK - JEP-286 og JEP-323.

2.1. Lokal variabel type indledning

Java 7 introducerede Diamond Operator som en måde at gøre generiske stoffer lettere at arbejde med. Denne funktion betyder, at vi ikke længere behøver at skrive generiske oplysninger flere gange i samme udsagn, når vi definerer variabler:

Listestrenge = ny ArrayList (); // Java 6 List strings = new ArrayList (); // Java 7

Java 10 inkluderede det afsluttede arbejde med JEP-286, hvilket giver mulighed for, at vores Java-kode definerer lokale variabler uden at skulle duplikere typeoplysningerne, hvor compileren allerede har den. Dette kaldes i det bredere samfund som var nøgleord og bringer lignende funktionalitet til Java som tilgængelig på mange andre sprog.

Med dette arbejde når vi definerer en lokal variabel, kan vi bruge var nøgleord i stedet for den fulde type definition, og compileren udarbejder automatisk den korrekte typeinformation, der skal bruges:

var strings = new ArrayList ();

I ovenstående er variablen strenge er bestemt til at være af typen ArrayList (), men uden at skulle kopiere oplysningerne på samme linje.

Vi kan bruge dette overalt, hvor vi bruger lokale variabler, uanset hvordan værdien bestemmes. Dette inkluderer returtyper og udtryk samt enkle opgaver som ovenstående.

Ordet var er et specielt tilfælde, idet det ikke er et forbeholdt ord. I stedet er det et specielt typenavn. Det betyder, at det er muligt at bruge ordet til andre dele af koden - inklusive variabelnavne. Det anbefales stærkt ikke at gøre dette for at undgå forvirring.

Vi kan kun bruge lokal type inferens, når vi angiver en faktisk type som en del af erklæringen. Det er bevidst designet til ikke at fungere, når værdien udtrykkeligt er angivet nul, når der overhovedet ikke gives nogen værdi, eller når den angivne værdi ikke kan bestemme en nøjagtig type - for eksempel en Lambda-definition:

var ukendtType; // Ingen værdi angivet for at udlede typen fra var nullType = null; // Eksplicit værdi, men den er nul var lambdaType = () -> System.out.println ("Lambda"); // Lambda uden at definere grænsefladen

Imidlertid, værdien kan være nul hvis det er en returværdi fra et andet opkald da selve opkaldet giver typeoplysninger:

Valgfrit navn = Optional.empty (); var nullName = name.orElse (null);

I dette tilfælde, nullnavn vil udlede typen Snor fordi det er hvad returtypen af name.orElse () er.

Variabler, der er defineret på denne måde, kan have andre modifikatorer på samme måde som enhver anden variabel - for eksempel, transitiv, synkroniseret, og endelig.

2.2. Lokal variabel type indledning for Lambdas

Ovenstående arbejde giver os mulighed for at erklære lokale variabler uden at skulle duplikere typeoplysninger. Dette fungerer dog ikke på parameterlister og især ikke på parametre for lambda-funktioner, hvilket kan virke overraskende.

I Java 10 kan vi definere Lambda-funktioner på en af ​​to måder - enten ved eksplicit at erklære typerne eller ved fuldstændigt at udelade dem:

names.stream () .filter (String name -> name.length ()> 5) .map (name -> name.toUpperCase ());

Her har den anden linje en eksplicit typedeklaration - Snor - hvorimod den tredje linje udelader det fuldstændigt, og kompilatoren udarbejder den korrekte type. Hvad vi ikke kan gøre er at bruge var Skriv her.

Java 11 tillader dette at ske, så vi kan i stedet skrive:

names.stream () .filter (var name -> name.length ()> 5) .map (var name -> name.toUpperCase ());

Dette er derefter i overensstemmelse med brugen af var skriv andetsteds i vores kode.

Lambdas har altid begrænset os til at bruge fulde typenavne enten for hver parameter eller for ingen af ​​dem. Dette har ikke ændret sig, og brugen af var skal være til hver eneste parameter eller ingen af ​​dem:

numbers.stream () .reduce (0, (var a, var b) -> a + b); // Gyldige tal. Stream () .reduce (0, (var a, b) -> a + b); // Ugyldige tal. Stream () .reduce (0, (var a, int b) -> a + b); // Ugyldig

Her er det første eksempel perfekt gyldigt - fordi de to lambda-parametre begge bruger var. Den anden og tredje er imidlertid ulovlige, fordi kun en parameter bruger var, selvom vi i det tredje tilfælde også har et eksplicit typenavn.

3. Overhængende opdateringer

Ud over de opdateringer, der allerede er tilgængelige i frigivne JDK'er, inkluderer den kommende JDK 12-udgivelse en opdatering - JEP-325.

3.1. Skift udtryk

JEP-325 giver støtte til at forenkle den måde, hvorpå kontakt udsagn fungerer og for at tillade dem at blive brugt som udtryk for yderligere at forenkle koden, der bruger dem.

På nuværende tidspunkt er den kontakt udsagn fungerer på en meget lignende måde som på sprog som C eller C ++. Disse ændringer gør det meget mere ligner hvornår erklæring i Kotlin eller match erklæring i Scala.

Med disse ændringer, syntaksen til definition af en switch-sætning ligner lambdas, med brug af -> symbol. Dette sidder mellem case match og koden, der skal udføres:

switch (måned) {case FEBRUAR -> System.out.println (28); sag APRIL -> System.out.println (30); sag JUNI -> System.out.println (30); sag SEPTEMBER -> System.out.println (30); sag NOVEMBER -> System.out.println (30); standard -> System.out.println (31); }

Bemærk, at pause nøgleord er ikke nødvendigt, og hvad mere er, vi kan ikke bruge det her. Det antydes automatisk, at hver kamp er særskilt, og gennembrud ikke er en mulighed. I stedet kan vi fortsætte med at bruge den ældre stil, når vi har brug for det.

Højre side af pilen skal enten være et udtryk, en blok eller et kast-udsagn. Alt andet er en fejl. Dette løser også problemet med at definere variabler inde i switch-sætninger - der kan kun ske inde i en blok, hvilket betyder, at de automatisk scoped til den blok:

skifte (måned) {sag FEBRUAR -> {int dage = 28; } sag APRIL -> {int dage = 30; } ....}

I den ældre stil-switch-sætning ville dette være en fejl på grund af duplikatvariablen dage. Kravet om at bruge en blok undgår dette.

Venstre side af pilen kan være et vilkårligt antal komma-adskilte værdier. Dette er for at tillade noget af den samme funktionalitet som gennembrud, men kun for hele en kamp og aldrig ved et uheld:

switch (måned) {case FEBRUAR -> System.out.println (28); sag APRIL, JUNI, SEPTEMBER, NOVEMBER -> System.out.println (30); standard -> System.out.println (31); }

Indtil videre er alt dette muligt med den nuværende måde at kontakt udsagn fungerer og gør det mere ryddeligt. Imidlertid, denne opdatering giver også muligheden for at bruge en kontakt udsagn som udtryk. Dette er en betydelig ændring for Java, men det er i overensstemmelse med, hvor mange andre sprog - inklusive andre JVM-sprog - der begynder at arbejde.

Dette giver mulighed for kontakt udtryk for at løse til en værdi og derefter bruge denne værdi i andre udsagn - for eksempel en opgave:

final var dage = switch (måned) {case FEBRUAR -> 28; sag APRIL, JUNI, SEPTEMBER, NOVEMBER -> 30; standard -> 31; }

Her bruger vi en kontakt udtryk for at generere et tal, og så tildeler vi dette nummer direkte til en variabel.

Før var dette kun muligt ved at definere variablen dage som nul og derefter tildele det en værdi inden i kontakt sager. Det betød det dage kunne ikke være endelig og kunne potentielt være ikke tildelt, hvis vi savnede en sag.

4. Kommende ændringer

Indtil videre er alle disse ændringer enten allerede tilgængelige eller vil være i den kommende udgivelse. Der er nogle foreslåede ændringer som en del af Project Amber, som endnu ikke er planlagt til frigivelse.

4.1. Rå strenglitteraturer

På nuværende tidspunkt har Java nøjagtigt en måde at definere en streng bogstavelig - ved at omgive indholdet i dobbelt anførselstegn. Dette er let at bruge, men det lider af problemer i mere komplicerede tilfælde.

Specifikt det er svært at skrive strenge, der indeholder bestemte tegn - inklusive men ikke begrænset til: nye linjer, dobbelt anførselstegn og tilbageslagstegn. Dette kan være særligt problematisk i filstier og regulære udtryk, hvor disse tegn kan være mere almindelige, end der er typisk.

JEP-326 introducerer en ny String-bogstavelig type kaldet Raw String Literals. Disse er omsluttet af backtick-mærker i stedet for dobbelt anførselstegn og kan overhovedet indeholde tegn inde i dem.

Dette betyder, at det bliver muligt at skrive strenge, der spænder over flere linjer, samt strenge, der indeholder anførselstegn eller tilbageslag uden at skulle undslippe dem. Således bliver de lettere at læse.

For eksempel:

// Filsystemsti "C: \ Dev \ file.txt" `C: \ Dev \ file.txt` // Regex" \ d + \. \ d \ d "` d + \. \ d \ d // // Multi-Line "Hello \ nWorld" `Hello World`

I alle tre tilfælde Det er lettere at se, hvad der sker i versionen med backticks, hvilket også er meget mindre tilbøjeligt til at skrive ud.

De nye Raw String Literals tillader os også at inkludere backticks selv uden komplikationer. Antallet af backticks, der bruges til at starte og afslutte strengen, kan være så længe som ønsket - det behøver ikke kun være et backtick. Strengen slutter kun, når vi når lige lang længde af backticks. Så for eksempel:

`` Denne streng tillader en enkelt '' `` fordi den er pakket i to backticks ''

Disse giver os mulighed for at skrive strenge nøjagtigt som de er, snarere end nogensinde at have brug for specielle sekvenser for at få bestemte tegn til at fungere.

4.2. Lambda rester

JEP-302 introducerer nogle små forbedringer af den måde, lambdas fungerer på.

De væsentligste ændringer er den måde, hvorpå parametre håndteres. For det første, denne ændring introducerer muligheden for at bruge en understregning til en ubrugt parameter, så vi ikke genererer navne, der ikke er nødvendige. Dette var muligt tidligere, men kun for en enkelt parameter, da en understregning var et gyldigt navn.

Java 8 introducerede en ændring, så brug af understregning som navn er en advarsel. Java 9 udviklede sig derefter til at blive en fejl i stedet for at forhindre os i at bruge dem overhovedet. Denne kommende ændring giver dem mulighed for lambda-parametre uden at forårsage nogen konflikter. Dette vil f.eks. Muliggøre følgende kode:

jdbcTemplate.queryForObject ("SELECT * FROM users WHERE user_id = 1", (rs, _) -> parseUser (rs))

Under denne forbedring, vi definerede lambda med to parametre, men kun den første er bundet til et navn. Det andet er ikke tilgængeligt, men ligeledes har vi skrevet det på denne måde, fordi vi ikke har noget behov for at bruge det.

Den anden store ændring i denne forbedring er at lade lambda-parametre skygge navne fra den aktuelle kontekst. Dette er i øjeblikket ikke tilladt, hvilket kan få os til at skrive noget mindre end ideel kode. For eksempel:

Strengnøgle = computeSomeKey (); map.computeIfAbsent (nøgle, nøgle2 -> nøgle2.længde ());

Der er ikke noget reelt behov, bortset fra kompilatoren, hvorfor nøgle og nøgle2 kan ikke dele et navn. Lambda behøver aldrig at henvise til variablen nøgleog at tvinge os til at gøre dette gør koden grimere.

I stedet giver denne forbedring os mulighed for at skrive det på en mere åbenbar og enkel måde:

Strengnøgle = computeSomeKey (); map.computeIfAbsent (nøgle, nøgle -> nøglelængde ());

Derudover der er en foreslået ændring i denne forbedring, der kan påvirke overbelastningsopløsning, når en overbelastet metode har et lambda-argument. På nuværende tidspunkt er der tilfælde, hvor dette kan føre til tvetydighed på grund af de regler, hvorunder overbelastningsopløsning fungerer, og denne JEP kan justere disse regler lidt for at undgå noget af denne tvetydighed.

For eksempel, på nuværende tidspunkt anser compileren følgende metoder for at være tvetydige:

m (Prædikat ps) {...} m (Funktion fss) {...}

Begge disse metoder tager en lambda, der har en enkelt Snor parameter og har en ikke-ugyldig returtype. Det er indlysende for udvikleren, at de er forskellige - man returnerer en Snor, og den anden, a boolsk, men compileren vil behandle disse som tvetydige.

Denne JEP kan løse denne mangel og tillade, at denne overbelastning behandles eksplicit.

4.3. Mønster Matching

JEP-305 introducerer forbedringer på den måde, vi kan arbejde med forekomst af tvangsoperatør og automatisk type.

På nuværende tidspunkt er det nødvendigt, når vi sammenligner typer i Java forekomst af operatør for at se, om værdien er af den korrekte type, og derefter er vi nødt til at kaste værdien til den rigtige type:

if (obj instanceof String) {String s = (String) obj; // brug s}

Dette fungerer og forstås med det samme, men det er mere kompliceret end nødvendigt. Vi har nogle meget åbenlyse gentagelser i vores kode og derfor en risiko for at lade fejl krybe ind.

Denne forbedring foretager en lignende tilpasning til forekomst af operatør som tidligere lavet under prøv med ressourcer i Java 7. Med denne ændring bliver sammenlignings-, rollebesætnings- og variabeldeklarationen i stedet en enkelt erklæring:

hvis (obj instanceof String s) {// use s}

Dette giver os en enkelt erklæring uden duplikering og ingen risiko for, at fejl kryber indog udfører alligevel det samme som ovenstående.

Dette fungerer også korrekt på tværs af grene, så følgende fungerer:

hvis (obj instanceof String s) {// kan bruge s her} ellers {// kan ikke bruge s her}

Forbedringen fungerer også korrekt på tværs af forskellige omfangsgrænser efter behov. Variablen erklæret af forekomst af klausul vil korrekt skygge variabler, der er defineret uden for den, som forventet. Dette sker kun i den relevante blok, dog:

String s = "Hej"; if (obj instanceof String s) {// s refererer til obj} ellers {// s henviser til den variabel, der er defineret før if-sætningen}

Dette fungerer også inden for det samme hvis klausul, på samme måde som vi stoler på for nul kontrol:

hvis (obj instanceof String s && s.length ()> 5) {// s er en streng på mere end 5 tegn}

På nuværende tidspunkt er dette kun planlagt til hvis udsagn, men fremtidigt arbejde vil sandsynligvis udvide det til at arbejde med skifte udtryk såvel.

4.4. Kortfattede metodeorganer

JEP-udkast 8209434 er et forslag til støtte for forenklede metodedefinitioner, på en måde, der svarer til, hvordan lambdadefinitioner fungerer.

Lige nu kan vi definere en Lambda på tre forskellige måder: med en krop, som et enkelt udtryk eller som en metodehenvisning:

ToIntFunction lenFn = (String s) -> {return s.length (); }; ToIntFunction lenFn = (String s) -> s.length (); ToIntFunction lenFn = Streng :: længde;

Imidlertid, når det kommer til at skrive egentlige klassemetoder, skal vi i øjeblikket skrive dem ud fuldt ud.

Dette forslag skal også understøtte udtryks- og metodereferenceformularerne for disse metoderi de tilfælde, hvor de finder anvendelse. Dette vil hjælpe med at holde visse metoder meget enklere, end de i øjeblikket er.

For eksempel behøver en getter-metode ikke en fuld metode-krop, men kan erstattes med et enkelt udtryk:

String getName () -> navn;

Ligeledes kan vi erstatte metoder, der simpelthen er indpakket omkring andre metoder med et metodereferenceopkald, herunder videregivelse af parametre på tværs af:

int længde (streng s) = streng :: længde

Disse giver mulighed for enklere metoder i de tilfælde, hvor de giver mening, hvilket betyder, at de er mindre tilbøjelige til at skjule den virkelige forretningslogik i resten af ​​klassen.

Bemærk, at dette stadig er i kladdestatus og som sådan kan ændres væsentligt inden levering.

5. Forbedrede Enums

JEP-301 var tidligere planlagt til at være en del af Project Amber. Dette ville have medført nogle forbedringer af enums, der eksplicit tillader individuelle enum-elementer at have særskilt generisk typeinformation.

For eksempel vil det tillade:

enum Primitive {INT (Integer.class, 0) {int mod (int x, int y) {return x% y; } int tilføj (int x, int y) {return x + y; }}, FLOAT (Float.class, 0f) {lang tilføjelse (lang x, lang y) {return x + y; }}, ...; endelig klasse boxClass; endelig X defaultValue; Primitive (Class boxClass, X defaultValue) {this.boxClass = boxClass; this.defaultValue = defaultValue; }}

Desværre, eksperimenter med denne forbedring inden for Java-kompilatorapplikationen har bevist, at den er mindre levedygtig, end man tidligere havde troet. Tilføjelse af generisk typeinformation til enum-elementer gjorde det umuligt at derefter bruge disse enums som generiske typer på andre klasser - for eksempel EnumSet. Dette reducerer drastisk nytten af ​​forbedringen.

Som sådan, denne forbedring er i øjeblikket på vent, indtil disse detaljer kan udarbejdes.

6. Resume

Vi har dækket mange forskellige funktioner her. Nogle af dem er allerede tilgængelige, andre vil snart være tilgængelige, og endnu mere er planlagt til fremtidige udgivelser. Hvordan kan disse forbedre dine nuværende og fremtidige projekter?