Programmering

Skriv avhengighet i Java, del 2

Å forstå typekompatibilitet er grunnleggende for å skrive gode Java-programmer, men samspillet mellom avvik mellom Java-språkelementer kan virke veldig akademisk for uinnvidde. Denne todelte artikkelen er for programvareutviklere som er klare til å takle utfordringen! Del 1 avslørte de kovariante og kontravariant forholdet mellom enklere elementer som array-typer og generiske typer, så vel som det spesielle Java-språkelementet, jokertegnet. Del 2 utforsker typeavhengighet i Java Collections API, i generiske og lambda-uttrykk.

Vi hopper rett inn, så hvis du ikke allerede har lest del 1, anbefaler jeg at du starter der.

API-eksempler for kontrast

For vårt første eksempel, vurder Komparator versjon av java.util.Collections.sort (), fra Java Collections API. Denne metodens signatur er:

  ugyldig sortering (liste liste, komparator c) 

De sortere() metoden sorterer noe Liste. Vanligvis er det lettere å bruke den overbelastede versjonen, med signaturen:

 sorter (Liste) 

I dette tilfellet, utvides sammenlignbar uttrykker at sortere() kan bare kalles hvis de nødvendige metodesammenligningselementene (nemlig sammenligne med) har blitt definert i elementtypen (eller i supertypen takket være ? super T):

 sorter (heltalliste); // Integer implementerer Comparable sort (customerList); // fungerer bare hvis kunden implementerer sammenlignbar 

Bruke generiske legemidler til sammenligning

Tydeligvis kan en liste bare sorteres hvis elementene kan sammenlignes mellom hverandre. Sammenligning gjøres etter den eneste metoden sammenligne med, som tilhører grensesnittet Sammenlignelig. Du må implementere sammenligne med i elementklassen.

Denne typen element kan imidlertid sorteres på en måte. For eksempel kan du sortere en Kunde etter ID, men ikke etter bursdag eller postnummer. Bruker Komparator versjon av sortere() er mer fleksibel:

 publicstatic void sort (Liste liste, Comparator c) 

Nå sammenligner vi elementer ikke i elementets klasse, men i et tillegg Komparator gjenstand. Dette generiske grensesnittet har en objektmetode:

 int sammenligne (To1, To2); 

Contravariant parametere

Hvis du installerer et objekt mer enn en gang, kan du sortere objekter etter forskjellige kriterier. Men trenger vi virkelig et så komplisert Komparator type parameter? I de fleste tilfeller, Komparator ville være nok. Vi kan bruke den sammenligne() metode for å sammenligne to elementer i Liste objekt, som følger:

klasse DateComparator implementerer Comparator {public int compare (Date d1, Date d2) {return ...} // sammenligner de to Date-objektene} List dateList = ...; // Liste over sortert datoobjekter (dateList, ny DateComparator ()); // sorterer dateList 

Ved hjelp av den mer kompliserte versjonen av metoden Collection.sort () sett oss opp for ytterligere brukstilfeller, imidlertid. Den motstridende typeparameteren til Sammenlignelig gjør det mulig å sortere en type liste Liste, fordi java.util.Date er en supertype av java.sql.dato:

 Liste sqlList = ...; sorter (sqlList, ny DateComparator ()); 

Hvis vi utelater motstrid i sortere() signatur (bruker bare eller det uspesifiserte, usikre ), deretter avviser kompilatoren den siste linjen som typefeil.

For å ringe

 sorter (sqlList, ny SqlDateComparator ()); 

du må skrive en ekstra karakterløs klasse:

 klasse SqlDateComparator utvider DateComparator {} 

Ytterligere metoder

Collections.sort () er ikke den eneste Java Collections API-metoden utstyrt med en kontravariant parameter. Metoder som Legg til alle(), binærsøk (), kopiere(), fylle(), og så videre, kan brukes med lignende fleksibilitet.

Samlinger metoder som maks () og min () tilby motstridende resultattyper:

 offentlig statisk  T max (samlingssamling) {...} 

Som du ser her, kan en type parameter bli bedt om å tilfredsstille mer enn én betingelse, bare ved å bruke &. De utvider Objekt kan virke overflødig, men det bestemmer at maks () returnerer et resultat av typen Gjenstand og ikke av rad Sammenlignelig i bytekoden. (Det er ingen typeparametere i bytekoden.)

Den overbelastede versjonen av maks () med Komparator er enda morsommere:

 offentlig statisk T max (Samlingssamling, Comparator comp) 

Dette maks () har begge motstridende og parametere for samvarianter. Mens elementene i Samling må være av (muligens forskjellige) undertyper av en bestemt (ikke eksplisitt gitt) type, Komparator må instantieres for en supertype av samme type. Mye kreves av kompilatorens inferensalgoritme for å skille denne mellomtypen fra en samtale som denne:

 Samlingssamling = ...; Comparator comparator = ...; maks (samling, komparator); 

Boxed binding av typeparametere

Som vårt siste eksempel på typeavhengighet og varians i Java Collections API, la oss revurdere signaturen til sortere() med Sammenlignelig. Merk at den bruker begge deler strekker og super, som er bokset:

 statisk  ugyldig sortering (Listeliste) {...} 

I dette tilfellet er vi ikke like interessert i kompatibiliteten til referanser som vi er i å binde instantiering. Denne forekomsten av sortere() metode sorterer a liste objekt med elementer fra en klasse som implementerer Sammenlignelig. I de fleste tilfeller vil sortering fungere uten i metodens signatur:

 sorter (dateList); // java.util.Date implementerer Comparable sort (sqlList); // java.sql.Date implementerer sammenlignbar 

Den nedre grensen for typeparameteren tillater imidlertid ekstra fleksibilitet. Sammenlignelig trenger ikke nødvendigvis implementeres i elementklassen; det er nok å ha implementert det i superklassen. For eksempel:

 klasse SuperClass implementerer Comparable {public int compareTo (SuperClass s) {...}} klasse SubClass utvider SuperClass {} // uten overbelastning av comparTo () Liste superList = ...; sorter (superliste); List subList = ...; sorter (underliste); 

Kompilatoren godtar den siste linjen med

 statisk  ugyldig sortering (liste liste) {...} 

og avviser det med

statisk  ugyldig sortering (Listeliste) {...} 

Årsaken til denne avvisningen er at typen Underklasse (som kompilatoren ville bestemme ut fra typen Liste i parameteren underliste) er ikke egnet som en typeparameter for T utvides sammenlignbar. Typen Underklasse implementerer ikke Sammenlignelig; det bare implementerer Sammenlignelig. De to elementene er ikke kompatible på grunn av mangel på implisitt kovarians, skjønt Underklasse er kompatibel med Superklasse.

På den annen side, hvis vi bruker , forventer ikke kompilatoren Underklasse å implementere Sammenlignelig; det er nok hvis Superklasse gjør det. Det er nok fordi metoden sammenligne med() er arvet fra Superklasse og kan kalles på Underklasse gjenstander: uttrykker dette og gir kontravaranse.

Contravariant tilgang variabler for en type parameter

Den øvre eller nedre grense gjelder bare for type parameter av instantiations henvist av en covariant eller kontravariant referanse. I tilfelle av Generisk kovariant Referanse; og Generisk kontravariantReferanse;, kan vi lage og henvise objekter av forskjellige Generisk instantiations.

Forskjellige regler er gyldige for parameteren og resultattypen til en metode (for eksempel for inngang og produksjon parametertyper av en generisk type). Et vilkårlig objekt som er kompatibelt med Undertype kan sendes som parameter for metoden skrive()som definert ovenfor.

 contravariantReference.write (ny undertype ()); // OK contravariantReference.write (ny SubSubType ()); // OK også contravariantReference.write (ny SuperType ()); // type feil ((Generisk) contravariantReference) .write (ny SuperType ()); // OK 

På grunn av kontravarians er det mulig å overføre en parameter til skrive(). Dette er i motsetning til den kovariante (også ubegrensede) jokertegnetypen.

Situasjonen endres ikke for resultattypen ved å binde: lese() leverer fremdeles et resultat av typen ?, bare kompatibel med Gjenstand:

 Objekt o = kontravariantReference.read (); Undertype st = contravariantReference.read (); // typefeil 

Den siste linjen gir en feil, selv om vi har erklært en kontravariantReferanse av typen Generisk.

Resultatet er kompatibel med en annen type bare etter referansetypen er eksplisitt konvertert:

 SuperSuperType sst = ((Generisk) kontravariantReferanse) .les (); sst = (SuperSuperType) contravariantReference.read (); // usikkert alternativ 

Eksempler i forrige oppføringer viser at lese- eller skrivetilgang til en variabel av typen parameter oppfører seg på samme måte, uavhengig av om det skjer over en metode (lese og skrive) eller direkte (data i eksemplene).

Lese og skrive til variabler av typeparameter

Tabell 1 viser at avlesning i en Gjenstand variabel er alltid mulig, fordi hver klasse og jokertegnet er kompatibelt med Gjenstand. Skrive en Gjenstand er bare mulig over en kontravariant referanse etter passende støping, fordi Gjenstand er ikke kompatibel med jokertegnet. Lesing uten å kaste inn i en uegnet variabel er mulig med en kovariant referanse. Skriving er mulig med en kontravariant referanse.

Tabell 1. Lese- og skrivetilgang til variabler av typeparameter

lesning

(inngang)

lese

Gjenstand

skrive

Gjenstand

lese

supertype

skrive

supertype

lese

undertype

skrive

undertype

Jokertegn

?

OK Feil Cast Cast Cast Cast

Kovariant

?strekker

OK Feil OK Cast Cast Cast

Contravariant

?super

OK Cast Cast Cast Cast OK

Radene i tabell 1 refererer til slags referanseog kolonnene til type data å få tilgang til. Overskriftene til "supertype" og "undertype" indikerer grensene for jokertegn. Oppføringen "rollebesetning" betyr at referansen må støpes. En forekomst av "OK" i de siste fire kolonnene refererer til typiske tilfeller for kovarians og kontravarans.

Se slutten av denne artikkelen for et systematisk testprogram for tabellen, med detaljerte forklaringer.

Å lage objekter

På den ene siden kan du ikke lage objekter av jokertegnetypen, fordi de er abstrakte. På den annen side kan du opprette matriseobjekter bare av en ubegrenset jokertegntype. Du kan imidlertid ikke lage objekter med andre generiske instantieringer.

 Generisk [] generiskArray = ny Generisk [20]; // typefeil Generisk [] wildcardArray = ny Generisk [20]; // OK genericArray = (Generisk []) jokertegnArray; // ukontrollert konvertering genericArray [0] = ny Generic (); genericArray [0] = ny Generic (); // type error wildcardArray [0] = new Generic (); // OK 

På grunn av samvarianten av matriser, typen jokertegn Generisk [] er supertypen til arraytypen til alle instantiations; Derfor er tildelingen i siste linje i ovennevnte kode mulig.

Innen en generell klasse kan vi ikke lage objekter av typeparameteren. For eksempel i konstruktøren av en ArrayList implementering, må array-objektet være av typen Gjenstand[] ved skapelsen. Vi kan deretter konvertere den til array-typen til typeparameteren:

 klasse MyArrayList implementerer List {private final E [] content; MyArrayList (int størrelse) {innhold = ny E [størrelse]; // type feilinnhold = (E []) nytt objekt [størrelse]; // løsning} ...} 

For å få en sikrere løsning, bestå Klasse verdien av den faktiske typeparameteren til konstruktøren:

 innhold = (E []) java.lang.reflect.Array.newInstance(myClass, størrelse); 

Flere typeparametere

En generisk type kan ha mer enn én type parameter. Typeparametere endrer ikke oppførselen til kovarians og kontravarians, og flere typeparametere kan forekomme sammen, som vist nedenfor:

 klasse G {} G referanse; referanse = ny G (); // uten variansreferanse = ny G (); // med med- og kontravarians 

Det generiske grensesnittet java.util.Kart brukes ofte som et eksempel for flere typeparametere. Grensesnittet har to typeparametere, en for nøkkel og en for verdi. Det er nyttig å knytte objekter til nøkler, for eksempel slik at vi lettere kan finne dem. En telefonbok er et eksempel på en Kart objekt ved hjelp av flere typeparametere: abonnentens navn er nøkkelen, telefonnummeret er verdien.

Grensesnittets implementering java.util.HashMap har en konstruktør for å konvertere en vilkårlig Kart objekt i en assosiasjonstabell:

 offentlig HashMap (Kart m) ... 

På grunn av kovarians trenger ikke parameterparameteren til parameterobjektet i dette tilfellet å samsvare med de nøyaktige typeparameterklassene K og V. I stedet kan den tilpasses gjennom kovarians:

 Kartkunder; ... kontakter = nye HashMap (kunder); // kovariant 

Her, Id er en supertype av Kundenummer, og Person er supertype av Kunde.

Variasjon i metoder

Vi har snakket om varians av typer; la oss nå vende oss til et noe enklere tema.

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