At forstå typekompatibilitet er grundlæggende for at skrive gode Java-programmer, men samspillet mellem afvigelser mellem Java-sprogelementer kan virke meget akademisk for de uindviede. Denne artikel i to dele er til softwareudviklere, der er klar til at tackle udfordringen! Del 1 afslørede de kovariante og kontravariant forhold mellem enklere elementer såsom matrixtyper og generiske typer samt det specielle Java-sprogelement, jokertegnet. Del 2 udforsker typeafhængighed i Java Collections API, i generiske og lambda-udtryk.
Vi springer lige ind, så hvis du ikke allerede har læst del 1, anbefaler jeg, at du starter der.
API-eksempler til kontrast
I vores første eksempel skal du overveje Komparator
version af java.util.Collections.sort ()
, fra Java Collections API. Denne metodes underskrift er:
ugyldig sortering (liste liste, komparator c)
Det sortere()
metode sorterer enhver Liste
. Normalt er det lettere at bruge den overbelastede version med signaturen:
sorter (Liste)
I dette tilfælde, udvides sammenlignelig
udtrykker, at sortere()
kan kun kaldes, hvis de nødvendige metodesammenligningselementer (nemlig sammenligne med)
er blevet defineret i elementtypen (eller i supertypen takket være ? super T)
:
sorter (heltalListe); // Integer implementerer Comparable sort (customerList); // fungerer kun, hvis kunden implementerer sammenlignelig
Brug af generiske stoffer til sammenligning
En liste kan naturligvis kun sorteres, hvis dens elementer kan sammenlignes mellem hinanden. Sammenligning udføres efter den enkelte metode sammenligne med
, som hører til grænsefladen Sammenlignelig
. Du skal implementere sammenligne med
i elementklassen.
Denne type element kan dog sorteres på en måde. For eksempel kan du sortere en Kunde
efter deres ID, men ikke efter fødselsdag eller postnummer. Bruger Komparator
version af sortere()
er mere fleksibel:
publicstatic void sort (Liste liste, Comparator c)
Nu sammenligner vi elementer ikke i elementets klasse, men i et ekstra Komparator
objekt. Denne generiske grænseflade har en objektmetode:
int sammenligne (Ti1, To2);
Modstridende parametre
Instantiering af et objekt mere end én gang giver dig mulighed for at sortere objekter ved hjælp af forskellige kriterier. Men har vi virkelig brug for en så kompliceret Komparator
type parameter? I de fleste tilfælde, Komparator
ville være nok. Vi kunne bruge det sammenligne()
metode til at 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-objekter} List dateList = ...; // Liste over sortering af datoobjekter (dateList, ny DateComparator ()); // sorterer dateList
Brug af den mere komplicerede version af metoden Collection.sort ()
sæt os dog op til yderligere brugssager. Parameteren for kontravariant af Sammenlignelig
gør det muligt at sortere en type liste Liste
, fordi java.util.Date
er en supertype af java.sql.dato
:
Liste sqlList = ...; sorter (sqlList, ny DateComparator ());
Hvis vi udelader modstrid i sortere()
underskrift (bruger kun eller det uspecificerede, usikre
), så afviser compileren den sidste linje som typefejl.
For at ringe
sorter (sqlList, ny SqlDateComparator ());
du bliver nødt til at skrive en ekstra klasseløs klasse:
klasse SqlDateComparator udvider DateComparator {}
Yderligere metoder
Collections.sort ()
er ikke den eneste Java Collections API-metode udstyret med en kontravariant parameter. Metoder som tilføjAlle ()
, binærsøgning ()
, kopi()
, fylde()
, og så videre, kan bruges med lignende fleksibilitet.
Samlinger
metoder som maks ()
og min ()
tilbyde kontravariant resultattyper:
offentlig statisk T max (samling samling) {...}
Som du ser her, kan en typeparameter anmodes om at tilfredsstille mere end en betingelse ved blot at bruge &
. Det udvider Objekt
kan virke overflødig, men det foreskriver det maks ()
returnerer et resultat af typen Objekt
og ikke af række Sammenlignelig
i bytekoden. (Der er ingen typeparametre i bytekode.)
Den overbelastede version af maks ()
med Komparator
er endnu sjovere:
offentlig statisk T max (samling samling, komparator comp)
Det her maks ()
har begge modstridende og parametre for covariant type. Mens elementerne i Kollektion
skal være af (muligvis forskellige) undertyper af en bestemt (ikke eksplicit angivet) type, Komparator
skal instantieres for en supertype af samme type. Der kræves meget af kompilatorens inferensalgoritme for at skelne denne mellemliggende type fra et opkald som dette:
Samlingssamling = ...; Komparator komparator = ...; max (samling, komparator);
Boxed binding af typeparametre
Som vores sidste eksempel på typeafhængighed og varians i Java Collections API, lad os genoverveje signaturen for sortere()
med Sammenlignelig
. Bemærk, at den bruger begge dele strækker sig
og super
, som er indrammet:
statisk ugyldig sortering (liste liste) {...}
I dette tilfælde er vi ikke så interesserede i referencernes kompatibilitet, som vi er i at binde instantieringen. Denne forekomst af sortere()
metode sorterer en liste
objekt med elementer i en klasse, der implementerer Sammenlignelig
. I de fleste tilfælde fungerer sortering uden i metodens underskrift:
sorter (dateList); // java.util.Date implementerer Comparable sort (sqlList); // java.sql.Date implementerer sammenlignelig
Den nedre grænse for typeparameteren tillader dog yderligere fleksibilitet. Sammenlignelig
behøver ikke nødvendigvis at blive implementeret i elementklassen; det er nok at have implementeret det i superklassen. For eksempel:
klasse SuperClass implementerer Comparable {public int compareTo (SuperClass s) {...}} klasse SubClass udvider SuperClass {} // uden overbelastning af comparTo () Liste superList = ...; sorter (superliste); Liste underliste = ...; sorter (underliste);
Compileren accepterer den sidste linje med
statisk ugyldig sortering (liste liste) {...}
og afviser det med
statisk ugyldig sortering (liste liste) {...}
Årsagen til denne afvisning er, at typen Underklasse
(som compileren ville bestemme ud fra typen Liste
i parameteren underliste
) er ikke egnet som en typeparameter til T strækker sig sammenlignelig
. Typen Underklasse
implementerer ikke Sammenlignelig
; det kun implementerer Sammenlignelig
. De to elementer er dog ikke kompatible på grund af manglen på implicit kovarians Underklasse
er kompatibel med Superklasse
.
På den anden side, hvis vi bruger , forventer ikke compileren
Underklasse
at implementere Sammenlignelig
; det er nok, hvis Superklasse
gør det. Det er nok, fordi metoden sammenligne med()
er arvet fra Superklasse
og kan kaldes til Underklasse
genstande: udtrykker dette og bevirker modstrid.
Contravariant adgang til variabler for en typeparameter
Den øvre eller nedre grænse gælder kun for type parameter af instantieringer henvist til med en covariant eller kontravariant reference. I tilfælde af Generisk covariantReference;
og Generisk kontravariantReference;
, kan vi oprette og henvise objekter af forskellige Generisk
instantiations.
Forskellige regler er gyldige for parameteren og resultattypen for en metode (f.eks. For input og produktion parametertyper af en generisk type). Et vilkårligt objekt, der er kompatibelt med Undertype
kan overføres som parameter for metoden skrive()
som defineret ovenfor.
contravariantReference.write (ny undertype ()); // OK contravariantReference.write (ny SubSubType ()); // OK også contravariantReference.write (ny SuperType ()); // typefejl ((Generisk) contravariantReference) .write (ny SuperType ()); // OKAY
På grund af modstrid er det muligt at overføre en parameter til skrive()
. Dette er i modsætning til den kovariante (også ubegrænsede) wildcard-type.
Situationen ændres ikke for resultattypen ved at binde: Læs()
leverer stadig et resultat af typen ?
, kun kompatibel med Objekt
:
Objekt o = kontravariantReference.read (); Undertype st = contravariantReference.read (); // typefejl
Den sidste linje producerer en fejl, selvom vi har erklæret en kontravariantReference
af typen Generisk
.
Resultatet er kompatibel med en anden type kun efter referencetypen er udtrykkeligt konverteret:
SuperSuperType sst = ((Generisk) kontravariantReference) .læs (); sst = (SuperSuperType) contravariantReference.read (); // usikre alternativ
Eksempler i de foregående lister viser, at læsning eller skriveadgang til en variabel af typen parameter
opfører sig på samme måde, uanset om det sker via en metode (læse og skrive) eller direkte (data i eksemplerne).
Læsning og skrivning til variabler af typen parameter
Tabel 1 viser, at aflæsning i en Objekt
variabel er altid mulig, fordi hver klasse og jokertegnet er kompatible med Objekt
. Skrivning af en Objekt
er kun mulig over en kontravariant reference efter passende støbning, fordi Objekt
er ikke kompatibel med jokertegnet. Læsning uden at caste i en uegnet variabel er mulig med en covariant reference. Skrivning er mulig med en kontravariant reference.
Tabel 1. Læse- og skriveadgang til variabler af typeparameter
læsning (input) | Læs Objekt | skrive Objekt | Læs supertype | skrive supertype | Læs undertype | skrive undertype |
Jokertegn
| Okay | Fejl | Cast | Cast | Cast | Cast |
Kovariant
| Okay | Fejl | Okay | Cast | Cast | Cast |
Modstridende
| Okay | Cast | Cast | Cast | Cast | Okay |
Rækkerne i tabel 1 henviser til slags referenceog kolonnerne til type data der skal tilgås. Overskrifterne på "supertype" og "subtype" angiver jokertegnets grænser. Posten "rollebesætning" betyder, at referencen skal støbes. En forekomst af "OK" i de sidste fire kolonner henviser til de typiske tilfælde for kovarians og kontrast.
Se slutningen af denne artikel for et systematisk testprogram til tabellen med detaljerede forklaringer.
Oprettelse af objekter
På den ene side kan du ikke oprette objekter af jokertegnetypen, fordi de er abstrakte. På den anden side kan du kun oprette matrixobjekter af en ubegrænset jokertegnetype. Du kan dog ikke oprette objekter med andre generiske instantieringer.
Generisk [] generiskArray = ny Generisk [20]; // typefejl Generisk [] wildcardArray = ny Generisk [20]; // OK genericArray = (Generisk []) jokertegnArray; // ukontrolleret konvertering genericArray [0] = ny Generic (); genericArray [0] = ny Generic (); // typefejl wildcardArray [0] = ny generisk (); // OKAY
På grund af arrays kovarians, type wildcard-array Generisk []
er supertypen af arraytypen for alle instantiations; derfor er tildelingen i den sidste linje i ovenstående kode mulig.
Inden for en generisk klasse kan vi ikke oprette objekter af typeparameteren. For eksempel i konstruktøren af en ArrayList
implementering, skal array-objektet være af typen Objekt[]
ved skabelsen. Vi kan derefter konvertere den til array-typen for typeparameteren:
klasse MyArrayList implementerer Liste {private final E [] indhold; MyArrayList (int størrelse) {indhold = ny E [størrelse]; // typefejlindhold = (E []) nyt objekt [størrelse]; // løsning} ...}
For at få en mere sikker løsning skal du passere Klasse
værdi af den aktuelle typeparameter til konstruktøren:
indhold = (E []) java.lang.reflect.Array.newInstance(myClass, størrelse);
Flere typeparametre
En generisk type kan have mere end én typeparameter. Typeparametre ændrer ikke adfærd for kovarians og kontrast, og flere typeparametre kan forekomme sammen som vist nedenfor:
klasse G {} G reference; reference = ny G (); // uden variansreference = ny G (); // med co- og kontrast
Den generiske grænseflade java.util.Kort
bruges ofte som et eksempel på flere typeparametre. Interfacet har to typeparametre, en for nøgle og en for værdi. Det er nyttigt at knytte objekter til nøgler, for eksempel så vi lettere kan finde dem. En telefonbog er et eksempel på en Kort
objekt ved hjælp af flere typeparametre: abonnentens navn er nøglen, telefonnummeret er værdien.
Interfaceens implementering java.util.HashMap
har en konstruktør til konvertering af en vilkårlig Kort
objekt i en associeringstabel:
offentlig HashMap (kort m) ...
På grund af kovarians behøver parameterparametrets type i dette tilfælde ikke at svare til de nøjagtige typeparameterklasser K
og V
. I stedet kan den tilpasses gennem kovarians:
Kortkunder; ... kontakter = nye HashMap (kunder); // kovariant
Her, Id
er en supertype af Kundenummer
og Person
er supertype af Kunde
.
Variant af metoder
Vi har talt om varians af typer; lad os nu henvende os til et noget lettere emne.