Inleiding tot Project Amber

1. Wat is Project Amber

Project Amber is een huidig ​​initiatief van de ontwikkelaars van Java en OpenJDK, met als doel enkele kleine maar essentiële wijzigingen aan de JDK aan te brengen om het ontwikkelingsproces leuker te maken. Dit is aan de gang sinds 2017 en heeft al enkele wijzigingen opgeleverd in Java 10 en 11, terwijl andere zijn gepland voor opname in Java 12 en nog meer in toekomstige releases.

Deze updates zijn allemaal verpakt in de vorm van JEP's - het JDK Enhancement Proposal-schema.

2. Geleverde updates

Tot op heden heeft Project Amber met succes enkele wijzigingen aangebracht in de momenteel uitgebrachte versies van de JDK - JEP-286 en JEP-323.

2.1. Inferentie van lokale variabele

Java 7 introduceerde de Diamond Operator als een manier om het werken met generieke geneesmiddelen gemakkelijker te maken. Deze functie betekent dat we generieke informatie niet langer meerdere keren in dezelfde instructie hoeven te schrijven wanneer we variabelen definiëren:

Lijststrings = nieuwe ArrayList (); // Java 6 List strings = nieuwe ArrayList (); // Java 7

Java 10 omvatte het voltooide werk op JEP-286, waardoor onze Java-code lokale variabelen kon definiëren zonder de type-informatie te hoeven dupliceren waar de compiler deze al beschikbaar heeft. Dit wordt in de bredere gemeenschap de var trefwoord en biedt vergelijkbare functionaliteit als Java, zoals beschikbaar in veel andere talen.

Met dit werk, telkens als we een lokale variabele definiëren, kunnen we de var trefwoord in plaats van de volledige typedefinitie, en de compiler berekent automatisch de juiste type-informatie om te gebruiken:

var strings = nieuwe ArrayList ();

In het bovenstaande is de variabele snaren is vastbesloten van type te zijn ArrayList (), maar zonder de informatie op dezelfde regel te hoeven dupliceren.

We kunnen dit overal gebruiken waar we lokale variabelen gebruiken, ongeacht hoe de waarde wordt bepaald. Dit omvat retourtypen en uitdrukkingen, evenals eenvoudige toewijzingen zoals de bovenstaande.

Het woord var is een speciaal geval, omdat het geen gereserveerd woord is. In plaats daarvan is het een speciale typenaam. Dit betekent dat het mogelijk is om het woord voor andere delen van de code te gebruiken, inclusief variabelenamen. Het wordt ten zeerste aanbevolen om dit niet te doen om verwarring te voorkomen.

We kunnen alleen inferentie van het lokale type gebruiken als we een actueel type opgeven als onderdeel van de declaratie. Het is opzettelijk ontworpen om niet te werken als de waarde expliciet is nul, wanneer er helemaal geen waarde wordt opgegeven, of wanneer de opgegeven waarde geen exact type kan bepalen - bijvoorbeeld een Lambda-definitie:

var unknownType; // Geen waarde opgegeven om het type af te leiden uit var nullType = null; // Expliciete waarde opgegeven, maar het is nul var lambdaType = () -> System.out.println ("Lambda"); // Lambda zonder de interface te definiëren

Echter, de waarde kan zijn nul als het een retourwaarde is van een andere aanroep aangezien de oproep zelf type-informatie biedt:

Optionele naam = Optioneel.empty (); var nullName = naam.orElse (null);

In dit geval, nullNaam zal het type afleiden Draad want dat is wat het retourtype is naam.orElse () is.

Variabelen die op deze manier zijn gedefinieerd, kunnen op dezelfde manier als elke andere variabele andere modificatoren hebben - bijvoorbeeld, transitief, gesynchroniseerd, en laatste.

2.2. Inferentie van type lokale variabele voor Lambdas

Het bovenstaande werk stelt ons in staat om lokale variabelen te declareren zonder de type-informatie te hoeven dupliceren. Dit werkt echter niet op parameterlijsten, en in het bijzonder niet op parameters voor lambda-functies, wat misschien verrassend lijkt.

In Java 10 kunnen we Lambda-functies op twee manieren definiëren - door de typen expliciet te declareren of door ze volledig weg te laten:

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

Hier heeft de tweede regel een expliciete typeaanduiding - Draad - terwijl de derde regel het volledig weglaat, en de compiler het juiste type uitwerkt. Wat we niet kunnen doen, is de var typ hier.

Java 11 laat dit gebeuren, dus we kunnen in plaats daarvan schrijven:

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

Dit is dan consistent met het gebruik van de var typ ergens anders in onze code.

Lambda's hebben ons altijd beperkt tot het gebruik van volledige typenamen, hetzij voor elke parameter, of voor geen ervan. Dit is niet veranderd, en het gebruik van var moet voor elke parameter zijn of geen ervan:

numbers.stream () .reduce (0, (var a, var b) -> a + b); // Geldige nummers.stream () .reduce (0, (var a, b) -> a + b); // Ongeldige nummers. Stream () .reduce (0, (var a, int b) -> a + b); // Ongeldig

Hier is het eerste voorbeeld volkomen geldig - omdat de twee lambda-parameters beide gebruiken var. De tweede en derde zijn echter illegaal, omdat slechts één parameter gebruikt var, ook al hebben we in het derde geval ook een expliciete typenaam.

3. Aanstaande updates

Naast de updates die al beschikbaar zijn in uitgebrachte JDK's, bevat de aankomende JDK 12-release één update - JEP-325.

3.1. Schakel tussen uitdrukkingen

JEP-325 biedt ondersteuning om de manier waarop dat te vereenvoudigen schakelaar uitspraken werken, en om toe te staan ​​dat ze als uitdrukkingen worden gebruikt om de code die er gebruik van maakt nog verder te vereenvoudigen.

Momenteel is het schakelaar statement werkt op een vergelijkbare manier als die in talen zoals C of C ++. Door deze wijzigingen lijkt het veel meer op het wanneer verklaring in Kotlin of de bij elkaar passen verklaring in Scala.

Met deze veranderingen, de syntaxis voor het definiëren van een switch-instructie lijkt op die van lambdas, met behulp van de -> symbool. Dit zit tussen de case-match en de code die moet worden uitgevoerd:

switch (maand) {case FEBRUARI -> System.out.println (28); geval APRIL -> System.out.println (30); geval JUNI -> System.out.println (30); geval SEPTEMBER -> System.out.println (30); geval NOVEMBER -> System.out.println (30); standaard -> System.out.println (31); }

Merk op dat de breken trefwoord is niet nodig, en wat meer is, we kunnen het hier niet gebruiken. Het impliceert automatisch dat elke wedstrijd verschillend is en dat doorval geen optie is. In plaats daarvan kunnen we de oudere stijl blijven gebruiken wanneer we die nodig hebben.

De rechterkant van de pijl moet een uitdrukking, een blok of een worp-instructie zijn. Al het andere is een fout. Dit lost ook het probleem op van het definiëren van variabelen in switch-instructies - dat kan alleen binnen een blok gebeuren, wat betekent dat ze automatisch naar dat blok gaan:

switch (maand) {geval FEBRUARI -> {int dagen = 28; } geval APRIL -> {int dagen = 30; } ....}

In de oudere stijl switch-instructie zou dit een fout zijn vanwege de dubbele variabele dagen. De vereiste om een ​​blok te gebruiken, vermijdt dit.

De linkerkant van de pijl kan een willekeurig aantal door komma's gescheiden waarden zijn. Dit is om een ​​deel van dezelfde functionaliteit mogelijk te maken als fallthrough, maar alleen voor de hele wedstrijd en nooit per ongeluk:

switch (maand) {case FEBRUARI -> System.out.println (28); geval APRIL, JUNI, SEPTEMBER, NOVEMBER -> System.out.println (30); standaard -> System.out.println (31); }

Tot nu toe is dit allemaal mogelijk met de huidige manier schakelaar verklaringen werken en maken het netter. Echter, deze update biedt ook de mogelijkheid om een schakelaar statement als een uitdrukking. Dit is een belangrijke verandering voor Java, maar het komt overeen met hoeveel andere talen - inclusief andere JVM-talen - beginnen te werken.

Dit zorgt voor de schakelaar expressie om een ​​waarde op te lossen en die waarde vervolgens in andere instructies te gebruiken - bijvoorbeeld een opdracht:

final var days = switch (month) {case FEBRUARI -> 28; geval APRIL, JUNI, SEPTEMBER, NOVEMBER -> 30; standaard -> 31; }

Hier gebruiken we een schakelaar expressie om een ​​getal te genereren, en dan wijzen we dat getal rechtstreeks toe aan een variabele.

Voorheen was dit alleen mogelijk door de variabele te definiëren dagen net zo nul en het vervolgens een waarde toewijzen binnen de schakelaar gevallen. Dat betekende dat dagen kon niet definitief zijn en kan mogelijk worden ingetrokken als we een zaak missen.

4. Aankomende veranderingen

Tot nu toe zijn al deze wijzigingen al beschikbaar of zullen ze in de komende release verschijnen. Er zijn enkele voorgestelde wijzigingen als onderdeel van Project Amber die nog niet gepland staan ​​voor release.

4.1. Raw String Literals

Momenteel heeft Java precies één manier om een ​​letterlijke tekenreeks te definiëren: door de inhoud tussen dubbele aanhalingstekens te plaatsen. Dit is gemakkelijk te gebruiken, maar het heeft problemen in meer gecompliceerde gevallen.

Specifiek, het is moeilijk om strings te schrijven die bepaalde karakters bevatten - inclusief maar niet beperkt tot: nieuwe regels, dubbele aanhalingstekens en backslash-tekens. Dit kan vooral problematisch zijn in bestandspaden en reguliere expressies waar deze tekens vaker voorkomen dan normaal.

JEP-326 introduceert een nieuw String-literaal type genaamd Raw String Literals. Deze staan ​​tussen backtick-markeringen in plaats van dubbele aanhalingstekens en kunnen alle tekens erin bevatten.

Dit betekent dat het mogelijk wordt om strings te schrijven die zich over meerdere regels uitstrekken, evenals strings die aanhalingstekens of backslashes bevatten zonder ze te moeten escapen. Zo worden ze gemakkelijker te lezen.

Bijvoorbeeld:

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

In alle drie gevallen het is gemakkelijker om te zien wat er aan de hand is in de versie met de backticks, wat ook veel minder foutgevoelig is om uit te typen.

De nieuwe Raw String Literals stellen ons ook in staat om de backticks zelf zonder complicaties op te nemen. Het aantal backticks dat wordt gebruikt om de string te starten en te beëindigen, kan zo lang zijn als gewenst - het hoeft niet slechts één backtick te zijn. De string eindigt pas als we een gelijke lengte aan backticks hebben bereikt. Dus bijvoorbeeld:

`` Deze string staat een enkele "" "toe omdat hij in twee backticks is gewikkeld ''

Deze stellen ons in staat om strings precies in te typen zoals ze zijn, in plaats van ooit speciale reeksen nodig te hebben om bepaalde karakters te laten werken.

4.2. Lambda restjes

JEP-302 introduceert enkele kleine verbeteringen in de manier waarop lambda's werken.

De belangrijkste veranderingen zijn de manier waarop met parameters wordt omgegaan. Ten eerste, deze wijziging introduceert de mogelijkheid om een ​​onderstrepingsteken te gebruiken voor een ongebruikte parameter, zodat we geen namen genereren die niet nodig zijn. Dit was voorheen mogelijk, maar alleen voor een enkele parameter, aangezien een onderstrepingsteken een geldige naam was.

Java 8 heeft een wijziging doorgevoerd, zodat het gebruik van een onderstrepingsteken als naam een ​​waarschuwing is. Java 9 heeft dit vervolgens doorgevoerd om in plaats daarvan een fout te worden, waardoor we ze helemaal niet meer kunnen gebruiken. Deze aanstaande wijziging stelt hen in staat lambda-parameters te gebruiken zonder conflicten te veroorzaken. Dit zou bijvoorbeeld de volgende code mogelijk maken:

jdbcTemplate.queryForObject ("SELECTEER * VAN gebruikers WAAR user_id = 1", (rs, _) -> parseUser (rs))

Onder deze verbetering, we hebben de lambda gedefinieerd met twee parameters, maar alleen de eerste is aan een naam gebonden. De tweede is niet toegankelijk, maar we hebben het ook op deze manier geschreven omdat we het niet nodig hebben.

De andere belangrijke wijziging in deze verbetering is om lambda-parameters namen te laten schaduwen vanuit de huidige context. Dit is momenteel niet toegestaan, wat ertoe kan leiden dat we een niet-ideale code schrijven. Bijvoorbeeld:

String key = computeSomeKey (); map.computeIfAbsent (key, key2 -> key2.length ());

Afgezien van de compiler is er geen echte behoefte waarom sleutel en sleutel 2 kan geen naam delen. De lambda hoeft nooit naar de variabele te verwijzen sleutel, en als je ons hiertoe dwingt, wordt de code lelijker.

In plaats daarvan stelt deze verbetering ons in staat om het op een meer voor de hand liggende en eenvoudige manier te schrijven:

String key = computeSomeKey (); map.computeIfAbsent (key, key -> key.length ());

Bovendien, er is een voorgestelde wijziging in deze verbetering die de overbelastingsresolutie zou kunnen beïnvloeden wanneer een overbelaste methode een lambda-argument heeft. Momenteel zijn er gevallen waarin dit tot dubbelzinnigheid kan leiden als gevolg van de regels waaronder overbelastingsresolutie werkt, en dit GEP kan deze regels enigszins aanpassen om een ​​deel van deze dubbelzinnigheid te vermijden.

Bijvoorbeeld, momenteel beschouwt de compiler de volgende methoden als dubbelzinnig:

m (Predicaat ps) {...} m (Functie fss) {...}

Beide methoden nemen een lambda met een enkele Draad parameter en heeft een niet-ongeldig retourtype. Het is voor de ontwikkelaar duidelijk dat ze verschillend zijn - men retourneert een Draad, en de andere, een boolean, maar de compiler zal deze als dubbelzinnig behandelen.

Dit GEP kan deze tekortkoming verhelpen en toestaan ​​dat deze overbelasting expliciet wordt behandeld.

4.3. Patroonaanpassing

JEP-305 introduceert verbeteringen in de manier waarop we kunnen werken met de instantie van operator en automatische typedwang.

Op dit moment moeten we bij het vergelijken van typen in Java de instantie van operator om te zien of de waarde van het juiste type is, en daarna moeten we de waarde naar het juiste type casten:

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

Dit werkt en wordt direct begrepen, maar het is ingewikkelder dan nodig is. We hebben een aantal zeer voor de hand liggende herhaling in onze code, en daarom een ​​risico dat fouten binnensluipen.

Deze verbetering maakt een vergelijkbare aanpassing aan de instantie van operator zoals eerder gemaakt onder probeer-met-middelen in Java 7. Met deze wijziging worden de vergelijking, cast en variabele declaratie in plaats daarvan een enkele instructie:

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

Dit geeft ons een enkele verklaring, zonder duplicatie en zonder risico dat er fouten binnensluipen, en toch presteert het hetzelfde als het bovenstaande.

Dit zal ook correct werken tussen branches, waardoor het volgende kan werken:

if (obj instanceof String s) {// kan hier s gebruiken} else {// kan s hier niet gebruiken}

De verbetering zal ook correct werken over verschillende bereikgrenzen heen, indien van toepassing. De variabele gedeclareerd door de instantie van clausule correct schaduwvariabelen die daarbuiten zijn gedefinieerd, zoals verwacht. Dit gebeurt echter alleen in het juiste blok:

String s = "Hallo"; if (obj instanceof String s) {// s verwijst naar obj} else {// s verwijst naar de variabele die is gedefinieerd voor de if-instructie}

Dit werkt ook binnen hetzelfde als clausule, op dezelfde manier waarop we vertrouwen voor nul controleert:

if (obj instanceof String s && s.length ()> 5) {// s is een String van meer dan 5 tekens}

Momenteel is dit alleen gepland voor als verklaringen, maar toekomstig werk zal het waarschijnlijk uitbreiden om mee te werken wissel uitdrukkingen ook.

4.4. Beknopte methodiekorganen

JEP Draft 8209434 is een voorstel om vereenvoudigde methodedefinities te ondersteunen, op een manier die vergelijkbaar is met hoe lambda-definities werken.

Op dit moment kunnen we een Lambda op drie verschillende manieren definiëren: met een hoofdtekst, als een enkele uitdrukking of als referentiemethode:

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

Echter, als het gaat om het schrijven van werkelijke klassenmethode-lichamen, moeten we ze momenteel volledig uitschrijven.

Dit voorstel is ter ondersteuning van de uitdrukkings- en methode-referentieformulieren voor deze methoden, in de gevallen waarin ze van toepassing zijn. Dit zal helpen om bepaalde methoden veel eenvoudiger te houden dan ze momenteel zijn.

Een getter-methode heeft bijvoorbeeld geen volledige body van de methode nodig, maar kan worden vervangen door een enkele uitdrukking:

String getName () -> naam;

Evenzo kunnen we methoden die simpelweg wrappers rond andere methoden zijn, vervangen door een methode-referentieaanroep, inclusief het doorgeven van parameters aan:

int length (String s) = String :: lengte

Deze maken eenvoudiger methoden mogelijk in de gevallen waarin ze zinvol zijn, wat betekent dat ze minder snel de echte bedrijfslogica in de rest van de klas zullen verdoezelen.

Houd er rekening mee dat dit nog steeds in conceptstatus is en als zodanig onderhevig is aan aanzienlijke wijzigingen voordat het wordt geleverd.

5. Verbeterde Enums

JEP-301 was eerder gepland om deel uit te maken van Project Amber. Dit zou enkele verbeteringen aan enums hebben gebracht, waardoor het expliciet mogelijk was dat individuele enum-elementen verschillende generieke typegegevens hebben.

Het zou bijvoorbeeld toestaan:

enum Primitief {INT (Geheel getal.klasse, 0) {int mod (int x, int y) {retour x% y; } int add (int x, int y) {return x + y; }}, FLOAT (Float.class, 0f) {long add (long x, long y) {return x + y; }}, ...; laatste klasse boxClass; laatste X defaultValue; Primitive (Class boxClass, X defaultValue) {this.boxClass = boxClass; this.defaultValue = defaultValue; }}

Helaas, experimenten met deze verbetering in de Java-compilertoepassing hebben bewezen dat het minder levensvatbaar is dan eerder werd gedacht. Door generieke type-informatie aan enum-elementen toe te voegen, was het onmogelijk om die enums vervolgens als generieke typen voor andere klassen te gebruiken, bijvoorbeeld EnumSet. Dit vermindert het nut van de verbetering drastisch.

Als zodanig, deze verbetering is momenteel opgeschort totdat deze details kunnen worden uitgewerkt.

6. Samenvatting

We hebben hier veel verschillende functies behandeld. Sommige zijn al beschikbaar, andere zullen binnenkort beschikbaar zijn, en er zijn er nog meer gepland voor toekomstige releases. Hoe kunnen deze uw huidige en toekomstige projecten verbeteren?


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