Programmering

Skriv afhængighed i Java, del 2

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

udvides

Okay Fejl Okay Cast Cast Cast

Modstridende

?super

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 Kundenummerog 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.

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