Introduksjon til Project Amber

1. Hva er Project Amber

Project Amber er et nåværende initiativ fra utviklerne av Java og OpenJDK, med sikte på å levere noen små, men viktige endringer i JDK for å gjøre utviklingsprosessen hyggeligere.. Dette har pågått siden 2017 og har allerede levert noen endringer i Java 10 og 11, med andre planlagt for inkludering i Java 12 og enda flere kommer i fremtidige utgivelser.

Disse oppdateringene er pakket sammen i form av JEPs - ordningen JDK Enhancement Proposal.

2. Leverte oppdateringer

Til dags dato har Project Amber med hell levert noen endringer i for øyeblikket utgitte versjoner av JDK - JEP-286 og JEP-323.

2.1. Lokal variabel type inferens

Java 7 introduserte Diamond Operator som en måte å gjøre generiske stoffer lettere å jobbe med. Denne funksjonen betyr at vi ikke lenger trenger å skrive generisk informasjon flere ganger i samme utsagn når vi definerer variabler:

Listestrenger = ny ArrayList (); // Java 6 Listestrenger = ny ArrayList (); // Java 7

Java 10 inkluderte det fullførte arbeidet med JEP-286, slik at Java-koden vår definerer lokale variabler uten å måtte duplisere typeinformasjonen der kompilatoren allerede har den tilgjengelig. Dette blir i det større samfunnet referert til som var nøkkelord og gir lignende funksjonalitet til Java som er tilgjengelig på mange andre språk.

Med dette arbeidet, når vi definerer en lokal variabel, kan vi bruke var nøkkelord i stedet for definisjonen av full type, og kompilatoren vil automatisk finne ut riktig typeinformasjon som skal brukes:

var strings = new ArrayList ();

I ovenstående, variabelen strenger er bestemt for å være av typen ArrayList (), men uten å måtte duplisere informasjonen på samme linje.

Vi kan bruke dette hvor som helst vi bruker lokale variabler, uavhengig av hvordan verdien bestemmes. Dette inkluderer returtyper og uttrykk, samt enkle oppgaver som ovenfor.

Ordet var er et spesielt tilfelle, fordi det ikke er et reservert ord. I stedet er det et spesielt typenavn. Dette betyr at det er mulig å bruke ordet for andre deler av koden - inkludert variabelnavn. Det anbefales sterkt å ikke gjøre dette for å unngå forvirring.

Vi kan bare bruke lokal typeslutning når vi oppgir en faktisk type som en del av erklæringen. Den er bevisst designet for ikke å fungere når verdien er eksplisitt null, når ingen verdi er gitt i det hele tatt, eller når den oppgitte verdien ikke kan bestemme en eksakt type - for eksempel en Lambda-definisjon:

var ukjentType; // Ingen verdi oppgitt for å utlede typen fra var nullType = null; // Eksplisitt verdi gitt, men den er null var lambdaType = () -> System.out.println ("Lambda"); // Lambda uten å definere grensesnittet

Derimot, verdien kan være null hvis det er en returverdi fra en annen samtale siden selve samtalen gir typeinformasjon:

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

I dette tilfellet, nullnavn vil utlede typen String fordi det er hva returtypen av name.orElse () er.

Variabler definert på denne måten kan ha andre modifikatorer på samme måte som alle andre variabler - for eksempel, transitive, synkroniserte, og endelig.

2.2. Lokal variabel type inferens for Lambdas

Ovennevnte arbeid lar oss erklære lokale variabler uten å måtte duplisere typeinformasjon. Dette fungerer imidlertid ikke på parameterlister, og spesielt ikke på parametere for lambda-funksjoner, noe som kan virke overraskende.

I Java 10 kan vi definere Lambda-funksjoner på en av to måter - enten ved eksplisitt å erklære typene eller ved å utelate dem helt:

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

Her har den andre linjen en eksplisitt typedeklarasjon - String - mens den tredje linjen utelater den fullstendig, og kompilatoren utarbeider riktig type. Det vi ikke kan gjøre er å bruke var Skriv her.

Java 11 lar dette skje, slik at vi i stedet kan skrive:

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

Dette er i samsvar med bruken av var skriv andre steder i koden vår.

Lambdas har alltid begrenset oss til å bruke fullstendige typenavn, enten for hver parameter, eller for ingen av dem. Dette har ikke endret seg, og bruken av var må være for hver parameter eller ingen av dem:

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

Her er det første eksemplet helt gyldig - fordi de to lambdaparametrene begge bruker var. Den andre og den tredje er ulovlig, fordi bare en parameter bruker var, selv om vi i det tredje tilfellet også har et eksplisitt typenavn.

3. Overhengende oppdateringer

I tillegg til oppdateringene som allerede er tilgjengelige i utgitte JDK-er, inneholder den kommende JDK 12-utgivelsen en oppdatering - JEP-325.

3.1. Bytt uttrykk

JEP-325 gir støtte for å forenkle måten bytte om uttalelser fungerer, og for å la dem brukes som uttrykk for å ytterligere forenkle koden som bruker dem.

For tiden er den bytte om uttalelse fungerer på en veldig lik måte som på språk som C eller C ++. Disse endringene gjør det mye mer likt når uttalelse i Kotlin eller kamp uttalelse i Scala.

Med disse endringene, syntaksen for å definere en bryteruttalelse ligner på lambdas, med bruk av -> symbol. Dette sitter mellom saksmatchen og koden som skal utføres:

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

Merk at gå i stykker nøkkelord er ikke nødvendig, og hva mer, vi kan ikke bruke det her. Det antydes automatisk at hver kamp er distinkt og gjennombrudd ikke er et alternativ. I stedet kan vi fortsette å bruke den eldre stilen når vi trenger den.

Høyre side av pilen må enten være et uttrykk, en blokk eller en kastesetning. Alt annet er en feil. Dette løser også problemet med å definere variabler inne i bryteruttalelser - det kan bare skje inne i en blokk, noe som betyr at de automatisk blir rettet til den blokken:

bytte (måned) {sak FEBRUAR -> {int dager = 28; } sak APRIL -> {int dager = 30; } ....}

I den eldre setningsbryteruttalelsen ville dette være en feil på grunn av duplikatvariabelen dager. Kravet om å bruke en blokk unngår dette.

Venstre side av pilen kan være et hvilket som helst antall kommaadskilte verdier. Dette er for å tillate noe av den samme funksjonaliteten som gjennombrudd, men bare for hele en kamp og aldri ved et uhell:

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

Så langt er alt dette mulig med den nåværende måten bytte om uttalelser fungerer og gjør det ryddigere. Derimot, denne oppdateringen gir også muligheten til å bruke en bytte om uttalelse som uttrykk. Dette er en betydelig endring for Java, men det stemmer overens med hvor mange andre språk - inkludert andre JVM-språk - som begynner å fungere.

Dette gir mulighet for bytte om uttrykk for å løse til en verdi, og deretter bruke den verdien i andre utsagn - for eksempel en oppgave:

siste var dager = bytte (måned) {sak FEBRUAR -> 28; sak APRIL, JUNI, SEPTEMBER, NOVEMBER -> 30; standard -> 31; }

Her bruker vi en bytte om uttrykk for å generere et tall, og så tilordner vi det nummeret direkte til en variabel.

Før var dette bare mulig ved å definere variabelen dager som null og deretter tildele den en verdi inne i bytte om saker. Det betydde det dager kunne ikke være endelig, og potensielt kunne ikke tildeles hvis vi savnet en sak.

4. Kommende endringer

Så langt er alle disse endringene enten tilgjengelige eller vil være i den kommende utgivelsen. Det er noen foreslåtte endringer som en del av Project Amber som ennå ikke er planlagt for utgivelse.

4.1. Rå strenglitteratur

For tiden har Java nøyaktig en måte å definere en streng bokstavelig - ved å omgi innholdet i doble anførselstegn. Dette er enkelt å bruke, men det lider av problemer i mer kompliserte tilfeller.

Nærmere bestemt, det er vanskelig å skrive strenger som inneholder visse tegn - inkludert men ikke begrenset til: nye linjer, doble anførselstegn og tilbakeslagstegn. Dette kan være spesielt problematisk i filbaner og regulære uttrykk der disse tegnene kan være vanligere enn det som er vanlig.

JEP-326 introduserer en ny String literal type kalt Raw String Literals. Disse er omsluttet av bakstreker i stedet for dobbelt anførselstegn og kan inneholde tegn i det hele tatt inne i dem.

Dette betyr at det blir mulig å skrive strenger som strekker seg over flere linjer, samt strenger som inneholder anførselstegn eller tilbakeslag uten å måtte unnslippe dem. Dermed blir de lettere å lese.

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 tilfeller, det er lettere å se hva som skjer i versjonen med backticks, som også er mye mindre utsatt for å skrive ut.

De nye Raw String Literals tillater oss også å inkludere backticks selv uten komplikasjoner. Antall backticks som brukes til å starte og avslutte strengen kan være så lenge som ønsket - det trenger ikke bare være ett backtick. Strengen slutter bare når vi når like lang lengde. Så for eksempel:

`` Denne strengen tillater en enkelt '' `` fordi den er pakket inn i to backticks ''

Disse lar oss skrive strengene nøyaktig slik de er, i stedet for å trenge spesielle sekvenser for å få bestemte tegn til å fungere.

4.2. Lambda rester

JEP-302 introduserer noen små forbedringer av måten lambdas fungerer på.

De viktigste endringene er i måten parametere håndteres på. For det første, denne endringen introduserer muligheten til å bruke en understreking for en ubrukt parameter, slik at vi ikke genererer navn som ikke er nødvendige. Dette var mulig tidligere, men bare for en enkelt parameter, siden en understrekning var et gyldig navn.

Java 8 introduserte en endring slik at bruk av understreking som navn er en advarsel. Java 9 utviklet seg deretter til å bli en feil i stedet, og forhindret oss i å bruke dem i det hele tatt. Denne kommende endringen tillater dem lambdaparametere uten å forårsake konflikter. Dette vil for eksempel tillate følgende kode:

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

Under denne forbedringen, vi definerte lambda med to parametere, men bare den første er bundet til et navn. Det andre er ikke tilgjengelig, men vi har skrevet det på denne måten fordi vi ikke har noe behov for å bruke det.

Den andre store endringen i denne forbedringen er å tillate lambda-parametere å skygge navn fra den nåværende konteksten. Dette er foreløpig ikke tillatt, noe som kan føre til at vi skriver litt mindre enn ideell kode. For eksempel:

Strengnøkkel = computeSomeKey (); map.computeIfAbsent (key, key2 -> key2.length ());

Det er ikke noe reelt behov, bortsett fra kompilatoren, hvorfor nøkkel og nøkkel2 kan ikke dele et navn. Lambda trenger aldri å referere til variabelen nøkkel, og å tvinge oss til å gjøre dette gjør koden styggere.

I stedet lar denne forbedringen oss skrive det på en mer åpenbar og enkel måte:

Strengnøkkel = computeSomeKey (); map.computeIfAbsent (nøkkel, nøkkel -> nøkkel.lengde ());

I tillegg det er en foreslått endring i denne forbedringen som kan påvirke overbelastningsoppløsningen når en overbelastet metode har et lambda-argument. For tiden er det tilfeller der dette kan føre til tvetydighet på grunn av reglene som overbelastningsoppløsning fungerer under, og denne JEP kan justere disse reglene litt for å unngå noe av denne tvetydigheten.

For eksempel, For øyeblikket anser kompilatoren følgende metoder for å være tvetydige:

m (Predikat ps) {...} m (Funksjon fss) {...}

Begge disse metodene tar en lambda som har en singel String parameter og har en ikke-ugyldig returtype. Det er åpenbart for utvikleren at de er forskjellige - man returnerer a String, og den andre, a boolsk, men kompilatoren vil behandle disse som tvetydige.

Denne JEP kan løse denne mangelen og tillate at denne overbelastningen behandles eksplisitt.

4.3. Mønster Matching

JEP-305 introduserer forbedringer på måten vi kan jobbe med tilfelle av operatør og automatisk tvang.

For øyeblikket, når vi sammenligner typer i Java, må vi bruke tilfelle av operatør for å se om verdien er av riktig type, og etterpå må vi kaste verdien til riktig type:

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

Dette fungerer og blir umiddelbart forstått, men det er mer komplisert enn det som er nødvendig. Vi har noen veldig åpenbare gjentakelser i koden vår, og derfor risikerer vi å la feil krype inn.

Denne forbedringen gjør en lignende justering av tilfelle av som tidligere ble gjort under prøv-med-ressurser i Java 7. Med denne endringen blir sammenligning, rollebesetning og variabelerklæring en enkelt uttalelse i stedet:

if (obj instanceof String s) {// bruk s}

Dette gir oss en enkelt uttalelse, uten duplisering og ingen risiko for at feil sniker seg innog utfører likevel det samme som ovenfor.

Dette fungerer også riktig på tvers av grener, slik at følgende kan fungere:

hvis (obj instanceof String s) {// kan bruke s her} annet {// kan ikke bruke s her}

Forbedringen vil også fungere riktig på tvers av forskjellige omfangsgrenser etter behov. Variabelen erklært av tilfelle av paragraf vil skygge variabler som er definert utenfor den, som forventet. Dette vil bare skje i riktig blokk, skjønt:

String s = "Hei"; if (obj instanceof String s) {// s refererer til obj} else {// s refererer til variabelen definert før if-setningen}

Dette fungerer også innenfor det samme hvis klausul, på samme måte som vi stoler på for null sjekker:

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

For øyeblikket er dette bare planlagt for hvis uttalelser, men fremtidig arbeid vil sannsynligvis utvide det til å jobbe med bytt uttrykk også.

4.4. Kortfattede metode organer

JEP-utkast 8209434 er et forslag om å støtte forenklede metodedefinisjoner, på en måte som ligner på hvordan lambdadefinisjoner fungerer.

Akkurat nå kan vi definere en Lambda på tre forskjellige måter: med en kropp, som et enkelt uttrykk, eller som en metodehenvisning:

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

Derimot, når det gjelder å skrive faktiske klassemetoder, må vi for øyeblikket skrive dem ut i sin helhet.

Dette forslaget er å støtte uttrykk og metodereferanseskjemaer også for disse metodene, i tilfeller der de er aktuelle. Dette vil bidra til å holde visse metoder mye enklere enn de er i dag.

For eksempel trenger en getter-metode ikke en full metodekropp, men kan erstattes med et enkelt uttrykk:

String getName () -> navn;

På samme måte kan vi erstatte metoder som bare er wrappers rundt andre metoder med en metodereferanseanrop, inkludert å sende parametere over:

int lengde (streng s) = streng :: lengde

Disse vil tillate enklere metoder i tilfeller der de gir mening, noe som betyr at det er mindre sannsynlig at de vil skjule den virkelige forretningslogikken i resten av klassen.

Merk at dette fremdeles er i kladdestatus, og som sådan kan det endres betydelig før levering.

5. Enhanced Enums

JEP-301 var tidligere planlagt å være en del av Project Amber. Dette ville ha medført noen forbedringer i enums, noe som eksplisitt tillater at individuelle enum-elementer har tydelig generisk informasjon.

For eksempel vil det tillate:

enum Primitive {INT (Integer.class, 0) {int mod (int x, int y) {return x% y; } int legg til (int x, int y) {return x + y; }}, FLOAT (Float.class, 0f) {long add (long x, long y) {return x + y; }}, ...; endelig Class boxClass; endelig X defaultValue; Primitive (Class boxClass, X defaultValue) {this.boxClass = boxClass; this.defaultValue = standardverdi; }}

Dessverre, eksperimenter med denne forbedringen inne i Java-kompilatorapplikasjonen har bevist at den er mindre levedyktig enn tidligere antatt. Å legge til generisk informasjon om enum-elementer gjorde det umulig å deretter bruke disse enumene som generiske typer på andre klasser - for eksempel EnumSet. Dette reduserer nytten av forbedringen drastisk.

Som sådan, denne forbedringen er for øyeblikket på vent til disse detaljene kan utarbeides.

6. Sammendrag

Vi har dekket mange forskjellige funksjoner her. Noen av dem er allerede tilgjengelige, andre vil være tilgjengelige snart, og enda er flere planlagt for fremtidige utgivelser. Hvordan kan disse forbedre dine nåværende og fremtidige prosjekter?


$config[zx-auto] not found$config[zx-overlay] not found