Å 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
| OK | Feil | OK | Cast | Cast | Cast |
Contravariant
| 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.