Super-skønne nyheder til Java

Project Coin, som vil implementere små, men herlige ændringer i den kommende udgave af Java, er nu parat til at lægge en specifikation på bordet. Version2 kigger nærmere på nyhederne.

Lidt, men godt. Det kunne være mottoet for Project Coin, som vil implementere små, rare ændringer i den kommende version 7 af Java. Ifølge bloggen Artima skulle det nu være klarlagt, hvilke af projektets forslag, som klarer sig igennem nåleøjet.

Switch med strenge
Nu kan strenge benyttes som parameter i en switch-case-kontrolstruktur. Indtil videre har man kun kunne benytte integers. Længere er den historie ikke:

String s = ...
switch(s) {
case "foo":
processFoo(s);
break;
}

Slut med try-catch-tyranni
Når resurser skal frigives, ender man ofte med try-catch-blokke inde i en catch- og finally-blok. Faktisk er det lidt uklart, hvad det optimale idiom er i den situation, mener Joshua Bloch, som sad bag rattet i Java 1.3 til 1.5. (version 3 til 5 i den senere kronologi).

Nu rådes der bod på situationen med en ny konstruktion, som frigiver resurser på en civiliseret facon. Den har ovennævnte Joshua Bloch som bagmand, og er inspireret af C#'s tilsvarende using-nøgleord. I stedet for der her:


BufferedReader br = new BufferedReader(new FileReader(path));

try {
return br.readLine();
}
finally {
br.close();
}

  • kan man skrive det her:

try (BufferedReader br = new BufferedReader(
new FileReader(path)) {
return br.readLine();
}

Det klares med et nyt interface, Disposable, der implementeres af klasser med en close()-metode, som f.eks. stream-io-klasserne.

Nemmere typeinferens
Java er ikke ligefrem et af de mest kortfattede sprog. Snarere må man sige, at den gamle dame er lidt snakkesaglig i forhold til nye statiske sprog som Scala og F#, der er mere skarpe i replikken. Når det drejer sig om at instantiere parametriserede typer, kan det tit blive en større mundfuld, men nu skæres der ned.

I stedet for at skrive:

Map> anagrams =
new HashMap>();

  • kan man nøjes med det her:

Map> anagrams = new HashMap<>();

Dynamisk - ligesom i C# 4.0
Java lader sig stadig inspirere af rivalen C#, som jo netop er nedkommet i version 4, hvor det nye nøgleord "dynamic" giver nemmere interoperabilitet med dynamiske sprog. Tidligere har JVM'en fået en instruks med, som gør det nemmere at implementere dynamiske sprog i miljøet, og nu skal sproget Java altså også følge med.

Det betyder simpelthen, at hvis en variabel er af typen Dynamic (fra den nye pakke java.dyn), kan man kalde en hvilken som helst metode på variablen, og det kompilerer. Hvis det lyder som det rene galskab i den statiske verden, så venter der heldigvis solide smæk over fingrene til de sløsede, når tingene tjekkes under kørselstid, ligesom når der castes under kørslen. Det ser sådan ud:

Dynamic x = (hvilket som helst udtryk her);
Object y = x.foo("ABC").bar(42).baz();

N****emmere lister, sets og maps
En skøn nyhed er understøttelse af konstant-udtryk for lister, sets og maps, ligesom det kendes fra mange andre sprog. Det er lige ud af landevejen og ser sådan ud:

final List piDigits = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9];

final Set primes = { 2, 7, 31, 127, 8191, 131071, 524287 };

final Map platonicSolids = { 4 : "tetrahedron",
6 : "cube", 8 : "octahedron", 12 : "dodecahedron",
20 : "icosahedron"
};

I den trivielle afdeling er nyhederne advarsler, som gives når der mistes typeinformation i forbindelse med varargs-konstruktionen. Her flyttes advarslen fra metode-kaldet til metode-definitionen, så programmøren får et vink med en vognstang om, hvor den er gal. Det er "Crazybob" Lee, som står bag det forslag.

Endeligt bliver det nemmere at skrive læsbare talkonstanter. Binære tal kan skrives som 0b101 eller 0B101, og store heltal kan opdeles i grupper med understregning, som i 9_223_372_036_854_775_807L.

Tips og korrekturforslag til denne historie sendes til tip@version2.dk
Kommentarer (21)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
#1 Søren Mors

Fordelen ved at anvende tal i et switch statement er så vidt jeg husker at der kan genereres effektiv maskinkode til at lave valget. Den fordel forsvinder alligevel ved at tillade strenge, og hvorfor er man så ikke gået videre.

Det vill da ihvertfald være helt oplagt at tillade Enum's i et case statemtement, og der er vel ikke noget i vejen for at tillade andre muligheder.

  • 0
  • 0
#2 Søren Mors

Fordelen ved at anvende tal i et switch statement er så vidt jeg husker at der kan genereres effektiv maskinkode til at lave valget. Den fordel forsvinder alligevel ved at tillade strenge, og hvorfor er man så ikke gået videre.

Det vill da ihvertfald være helt oplagt at tillade Enum's i et case statemtement, og der er vel ikke noget i vejen for at tillade andre muligheder.

  • 0
  • 0
#6 Torben Mogensen Blogger

Hvorfor kan man ikke bruge alle typer, hvor der er konstantudtryk, og hvor lighed er defineret?

Så man kunne f.eks. lave en switch i stil med

[code=java] switch(l) { case []: ...; case [3,2]: ...; ... } [/code]

Og hvorfor kan man så ikke bruge variabler i cases, så man kan skrive noget i stil med

[code=java] switch(l) { case []: ...; case [x,y]: ... x ... y ...; ... } [/code]

Men så kunne man vel ligesågodt bruge ML, F#, Scala eller Haskell. :-)

  • 0
  • 0
#7 Christian Hvid

Torben. Som switch-statementet er designet, så fungerer det kun med primitive typer.

Skulle man udvide statementet til at fungere (meningsfuldt) med objekter, så vil man miste muligheden for en masse statiske check idet ækvalens ikke er defineret af sproget, men ved kald til equals-metoden.

I Java giver dette en fejl på kompileringstidspunktet:

final int I=2; final int J=3; final int K=2;

switch (i) { case I: case J: case K: }

Idet der er en "duplicate label" med I og K.

Var tallene i stedet for objekter, så skulle vi kalde I.equals(K) for at afgøre hvorvidt de to objekter var ens.

Som objekter fungerer, er det fuldstændigt lovligt at f.eks. equals åbner et dialogvindue og spørger brugeren hvorvidt de to objekter er ens, hvorfor det bliver umuligt at afgøre hvorvidt I er det samme som K på oversættelsestidspunktet.

  • 0
  • 0
#8 Torben Mogensen Blogger

Christian, du misforstår brugen af variabler i cases: Der bliver ikke matchet mod den værdi, variablen indeholder, der er tale om en ny lokal variabel, der bliver bundet til den delværdi af den matchede værdi, som svarer til positionen af variablen.

Men, som du siger, er der ikke en a priori ækvivalens defineret for alle typer. Det er dog ikke noget problem, da man blot skal kræve, at den type, der matches imod implementerer equals metoden. Det bliver dog lidt mere kompliceret, når der er variabler i mønstre: Der skal et mønster oversættes til kode, der tilgår de enkelte dele af værdien og tester dem mod de konstante dele af mønstret mens variabler i mønstret bliver bundet til de relevante dele af værdien. For lister skal man altså bruge en null-test, head og tail og en equals-metode for elementerne (hvis elementkonstanter forekommer i mønstrene). Det er ikke svært på oversættelsestid at finde de nødvendige krav, et mønster stiller til typen og rapportere typefejl, hvis de ikke er overholdt. Groft sagt definerer hvert mønster en interface, der skal overholdes.

  • 0
  • 0
#9 Christian Hvid

Torben.

"Christian, du misforstår brugen af variabler i cases: Der bliver ikke matchet mod den værdi, variablen indeholder, der er tale om en ny lokal variabel, der bliver bundet til den delværdi af den matchede værdi, som svarer til positionen af variablen."

Kunne du uddybe dette? Jeg tror ikke jeg forstår hvad du mener.

Anyway - I, J, K er alle erklæret final i mit eksempel for at illustrere at de kunne erstattes med disse "konstante" objekter, vi leger at udvide switch-konstruktionen til at understøtte. Koden kan (med god grund) ikke kompileres var variablerne ikke erklæret final.

Det andet du skriver kan jeg ikke se løser, det problem, jeg nævner: At afgørligheden af hvorvidt to objekter er ens flyttes til kørselstidpunktet, hvorfor vi mister muligheden for en serie statiske checks, der er en del af sproget nu.

  • 0
  • 0
#10 Casper Bang

Er jeg den eneste der er skuffet over disse mikro-tiltag? Efter 3 år med indtil flere closure prototyper der har stjålet rampelys fra andre forbedringer, får vi nu kun dette.

I det mindste kunne de have sørget for at vi fik multi-line strings og properties.

  • 0
  • 0
#11 Christian Hvid

PS: Jeg er glad for at man er superkonservativ med hvordan man udvider sproget, og en særskildt plads til eksperimenter (som Project Coin) er en god og konstruktiv måde at få testet og filtreret ideerne.

F.eks. så synes jeg at multiline-strenge er noget værre pjat og folk må smide deres tekster, der åbenbart er for lange til strengkonkatenation og \n, et andet sted end inde midt i Java-kildeteksten.

  • 0
  • 0
#12 Casper Bang

Christian, har du prøvet at vedhligeholde flere tusinde liniers JPQL/HQL kode? Fordi man i Java ikke kan udtrykke expression trees, er man nød til at begrave queries og andre ting inde i en streng på en annotation (såkaldt embedded DSL) som man hverken får formatteringshjælp til, syntaxhjælp til og først kan valideres ved runtime.

  • 0
  • 0
#13 Torben Mogensen Blogger

Kunne du uddybe dette?

Hvis x er [3,4] vil koden

[code=java] switch(x) { case [] => y=0; break; case [0,4] => y=4; break; case [p,q] => y=p+q; } [/code]

gå forbi første case, da x ikke er den tomme liste, og den anden case, da elementerne ikke er 0 og 4, men matche den tredje case, da x er en liste med to elementer. p bindes til 3 (det første element i x) og q bindes til 4 (det andet element). Dermed giver p+q lig med 7, så ved udgangen af koden er y lig med 7.

Man kan godt udvide notationen til at tillade sammenligning med variabler eller (mere generelt) test af værdier. Et mønster af formen a.equals ville f.eks. kalde a.equals med den tilsvarende del af værdien. For eksempel ville koden

[code=java] switch(x) { case a.equals => y=0; break; case b.less => y=1; break; case c => y=c; } [/code]

lade y=0 hvis a.equals(x) er sand, y=1 hvis b.less(x) er sand, og y=x hvis ingen af disse er sande (da c bliver bundet til x i den sidste case).

Jeg håber, at det gør det klarere.

  • 0
  • 0
#14 Christian Hvid

Multiline strings vil nu ikke løse det problem, du beskriver. Embedded DSLer er noget større problematik end bare at have strenge på mere end en linje.

Det det altid står folk frit for at gøre er at tage deres tekst, som f.eks. indeholder program-kode i skrevet i et andet sprog og placere det i en fil eller en ressource, der så bygges ind i distributablen.

Tit vil det også give bedre arkitektur og design.

Torben: Det går op for mig at du beskriver pattern matching.

Du spurgte "Hvorfor kan man ikke bruge alle typer, hvor der er konstantudtryk, og hvor lighed er defineret?" oprindeligt.

Jeg tror svaret er at så vil "switch" i Java pludseligt fungere helt og aldeles anderledes end det gør nu, man vil miste bagudkompabilitet og vil lægge op til en fundamentalt anderledes måde at udvikle på.

  • 0
  • 0
#15 Casper Bang

@Christian: Faktum: Vi bruger et embedded DSL fordi Java ikke har mulighed for samme elgance of type-sikkerhed som f.eks. LINQ. Men eftersom det vil være utopi at forestille sig sådan innovation i Java, må man ty til det næstbedste, misbrug af annotations.

Jeg kunne da også have taget fat i f.eks. BigDecimal og hvor utrolig rart det havde været med decimal literal support i Java, ligesom samtlige andre sprog jeg kender. Så kunne følgende:

BigDecimal resultat = new BigDecimal("12.34").add(new BigDecimal("34.45")).multiply(new BigDecimal("1.12")).subtract(new BigDecimal("3.21"));

...skrives således:

decimal resultat = (12.34m + 34.45m) * 1.12m - 3.21m;

Så jeg mener ikke der er ret meget at komme efter her i JDK7 bortset fra ARM forslaget. Dette vil desværre ikke virke med eksisterende libraries eftersom de er nød til at introducere et nyt interface Disposable, da det eksisterende Closable desværre smider en checked excaption (som i sig selv er et fejl eksperiment).

  • 0
  • 0
#16 Baldur Norddahl

Jeg tror svaret er at så vil "switch" i Java pludseligt fungere helt og aldeles anderledes end det gør nu, man vil miste bagudkompabilitet og vil lægge op til en fundamentalt anderledes måde at udvikle på.

Hvis man tillader noget der ikke før var tilladt, så mister man ikke bagudkompabilitet. Der er ingen der foreslår at switch med tal og uden variabler skal opføre sig anderledes.

I samme stil kunne man let fjerne kravet om at inner classes kun kan referere til final variabler fra den omkredsende metode. Altså tillade closure i inner classes. Det vil ikke påvirke bagudkompatibilitet da man tager noget der var forbudt og blot fjerner begrænsningen.

Selvom ækvivalens mellem objekter ikke er defineret, så er den defineret for tal. Dit eksempel med to case 2: vil derfor stadig kunne fanges af oversætteren. Kun hvis man bevæger sig ud i at udnytte de nye muligheder vil oversætteren miste mulighed for at verificere om to case er ens. Det er ikke et reelt problem hvis man bare definere at programmet altid vælger den første.

Alternativet er jo at skrive kode i stil med:

if (x.equals(a)) {...} else if (x.equals(b)) {...} else if (x.equals(c)) {...} ...

Og da får man heller ikke hjælp af oversætteren til at fange kode der ikke kan nås på grund af en tidligere betingelse.

  • 0
  • 0
#17 Casper Bang

I samme stil kunne man let fjerne kravet om at inner classes kun kan referere til final variabler fra den omkredsende metode. Altså tillade closure i inner classes. Det vil ikke påvirke bagudkompatibilitet da man tager noget der var forbudt og blot fjerner begrænsningen.

Enig, man kunne starte med at lempe en masse ting. Jeg har f.eks. hacked min egen javac for at konvertere checked exceptions (og lignende compiler tåbeligheder) til warnings istedet for errors: http://coffeecokeandcode.blogspot.com/2009/08/tweaking-javac-leniency.html

Dog tror jeg ikke det har nogen reel mulighed for at blive ændret i officiel Java, da 1) Sun er ultra konservative og 2) de har ingen resourcer til det, alt er satset på JavaFX (skriver Alex Buckley selv i en email korrespondance).

PS: Forøvrigt synd for Neal Gafter. Han er pt. javac's vedligholder, men fik ikke et eneste af sine 3 forslag igennem. Man kan så godt forstå han er skiftet til Microsoft.

  • 0
  • 0
#18 Christian Hvid

Baldur - det var nu mest pattern matching, jeg mente var en for stor / irrelevant mundfuld.

Strenge kan man selvfølgeligt fint have i switch-statements, C# har det.

Selvom jeg skulle mene at enums er en bedre løsning (tag strengen og lav den til en enum med EnumType.valueOf(xxx) og switch på den).

final løser et reelt problem ved inner classes / delegates omkring variablers skop, som andre sprog simpelthen bare ignorerer. Men det er en anden diskussion, som må vente ...

  • 0
  • 0
#19 Casper Bang

final løser et reelt problem ved inner classes / delegates omkring variablers skop, som andre sprog simpelthen bare ignorerer.

Næ sådan noget kan du da ikke komme med uden argumentation? :)

Kravet om final er der fordi Java compileren, ved compile tid, simpelthen erstatter variablen med en konstant. Konstanter er unikke ved at de IKKE lever på stakken.

Andre sprog ignorerer det ikke, de hoister bevidst variablen op i heapen istedet og lader standard garbage collection klare sagerne. Det er det samme der sker når du omgår denne Java begrænsning ved inner classes, ved at smide din variabel i et array.

  • 0
  • 0
#20 Baldur Norddahl

Casper har egentlig svaret på det, men her er en uddybning. [code=java] public Object Test(int x) { x = x*2; return new Object() { public int bar() { return x; } }; } [/code] Ovenstående fejler med "local variable x is accessed from within inner class; needs to be declared final"

Men fixet er trivielt og kunne udføres automatisk af oversætteren: [code=java] public Object Test(int x) { final Integer[] closure = new Integer[] { x }; closure[0] = closure[0]*2; return new Object() { public int bar() { return closure[0]; } }; } [/code]

Jeg ved ikke om det sker specielt ofte at man lige har behov for dette. På den anden side er det mærkværdigt at man har lavet sådan en begrænsning, tilsyneladende blot fordi man ikke havde tid til at kode det da man lavede oversætteren.

  • 0
  • 0
#21 Casper Bang

Jeg ved ikke om det sker specielt ofte at man lige har behov for dette.

Jeg har sjovt nok lige haft brug for det, men tyede til et andet trick. Bemærk linien "final int l = lower;"

[code=java] void processInParallel(ExecutorService exec, final int upper){ for(int lower = 0; lower < upper/CHUNK_SIZE; lower+=CHUNK_SIZE){ final int l = lower; exec.execute(new Runnable(){ @Override public void run(){ process(l, l+CHUNK_SIZE); } }); } } [/code]

  • 0
  • 0
Log ind eller Opret konto for at kommentere