Programmering

Skriv afhængighed i Java, del 1

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 er til softwareudviklere, der er klar til at tackle udfordringen! Del 1 afslører de kovariante og kontravariant forhold mellem enklere elementer såsom arraytyper og generiske typer såvel som det specielle Java-sprogelement, jokertegnet. Del 2 udforsker typeafhængighed og varians i almindelige API-eksempler og i lambda-udtryk.

download Download kilden Hent kildekoden til denne artikel, "Type afhængighed i Java, del 1." Oprettet til JavaWorld af Dr. Andreas Solymosi.

Begreber og terminologi

Før vi går ind i forholdet mellem kovarians og kontrast mellem forskellige Java-sprogelementer, lad os være sikre på, at vi har en fælles konceptuel ramme.

Kompatibilitet

I objektorienteret programmering, kompatibilitet henviser til et rettet forhold mellem typer, som vist i figur 1.

Andreas Solymosi

Vi siger, at to typer er kompatibel i Java, hvis det er muligt at overføre data mellem variabler af typerne. Dataoverførsel er mulig, hvis compileren accepterer det og sker gennem tildeling eller parameteroverføring. Som et eksempel, kort er kompatibel med int fordi opgaven intVariable = shortVariable; er muligt. Men boolsk er ikke kompatibel med int fordi opgaven intVariable = booleanVariable; er ikke muligt; compileren accepterer det ikke.

Fordi kompatibilitet nogle gange er et målrettet forhold T1 er kompatibel med T2 men T2 er ikke kompatibel med T1eller ikke på samme måde. Vi ser dette yderligere, når vi kommer til at diskutere eksplicit eller implicit kompatibilitet.

Det der betyder noget er, at kompatibilitet mellem referencetyper er mulig kun inden for et typehierarki. Alle klassetyper er kompatible med Objekt, for eksempel fordi alle klasser arver implicit fra Objekt. Heltal er ikke kompatibel med Flydedog fordi Flyde er ikke en superklasse af Heltal. Heltaler kompatibel med Nummer, fordi Nummer er en (abstrakt) superklasse af Heltal. Fordi de er placeret i samme typehierarki, accepterer compileren tildelingen numberReference = integerReference;.

Vi taler om implicit eller eksplicit kompatibilitet, afhængigt af om kompatibilitet skal markeres eksplicit eller ej. For eksempel er kort implicit kompatibel med int (som vist ovenfor) men ikke omvendt: opgaven shortVariable = intVariable; er ikke muligt. Men kort er eksplicit kompatibel med int, fordi opgaven shortVariable = (kort) intVariable; er muligt. Her skal vi markere kompatibilitet med støbning, også kendt som typekonvertering.

Tilsvarende blandt referencetyper: integerReference = antalReference; er kun acceptabelt integerReference = (Heltal) numberReference; ville blive accepteret. Derfor, Heltal er implicit kompatibel med Nummer men Nummer er kun eksplicit kompatibel med Heltal.

Afhængighed

En type kan afhænge af andre typer. For eksempel matrixtypen int [] afhænger af den primitive type int. Tilsvarende den generiske type ArrayList afhænger af typen Kunde. Metoder kan også være typeafhængige afhængigt af typen af ​​deres parametre. For eksempel metoden ugyldig stigning (heltal i); afhænger af typen Heltal. Nogle metoder (som nogle generiske typer) afhænger af mere end én type - såsom metoder, der har mere end en parameter.

Kovarians og kontravarans

Kovarians og kontravarans bestemmer kompatibilitet baseret på typer. I begge tilfælde er varians en rettet relation. Kovarians kan oversættes som "forskellige i samme retning" eller med-anderledes, hvorimod modstrid betyder "forskelligt i den modsatte retning" eller mod-forskellige. Kovariante og kontravariant typer er ikke de samme, men der er en sammenhæng mellem dem. Navnene antyder retningen af ​​sammenhængen.

Så, kovarians betyder, at kompatibiliteten af ​​to typer indebærer kompatibiliteten af ​​de typer, der er afhængige af dem. Givet typekompatibilitet antager man, at afhængige typer er kovariante, som vist i figur 2.

Andreas Solymosi

Kompatibiliteten af T1 til T2 indebærer kompatibilitet med 1) til 2). Den afhængige type PÅ) Hedder kovariant; eller mere præcist, 1) er kovariant til 2).

For et andet eksempel: fordi opgaven numberArray = heltalArray; er mulig (i det mindste i Java), arraytyperne Heltal[] og Nummer[] er kovariante. Så vi kan sige det Heltal[] er implicit kovariant til Nummer[]. Og mens det modsatte ikke er sandt - opgaven integerArray = antalArray; er ikke mulig - opgaven med type støbning (integerArray = (Heltal []) numberArray;) er muligt; derfor siger vi, Nummer[] er eksplicit kovariant til Heltal[] .

At opsummere: Heltal er implicit kompatibel med Nummer, derfor Heltal[] er implicit kovariant til Nummer[]og Nummer[] er eksplicit kovariant til Heltal[] . Figur 3 illustrerer.

Andreas Solymosi

Generelt kan vi sige, at arraytyper er covariante i Java. Vi vil se på eksempler på kovarians blandt generiske typer senere i artiklen.

Modstrid

Ligesom kovarians er kontravarians en instrueret forhold. Mens kovarians betyder med-anderledesbetyder kontrast mod-forskellige. Som jeg tidligere nævnte, navnene udtrykker retningen for sammenhængen. Det er også vigtigt at bemærke, at varians ikke generelt er en attribut for typer, men kun af afhængig typer (såsom arrays og generiske typer, og også af metoder, som jeg vil diskutere i del 2).

En afhængig type som f.eks PÅ) Hedder modstridende hvis kompatibiliteten af T1 til T2 indebærer kompatibilitet med 2) til 1). Figur 4 illustrerer.

Andreas Solymosi

Et sprogelement (type eller metode) PÅ) kommer an på T er kovariant hvis kompatibiliteten af T1 til T2 indebærer kompatibilitet med 1) til 2). Hvis kompatibiliteten af T1 til T2 indebærer kompatibilitet med 2) til 1), derefter typen PÅ) er modstridende. Hvis kompatibiliteten af T1 mellem T2 indebærer ikke nogen kompatibilitet mellem 1) og 2), derefter PÅ) er invariant.

Array-typer i Java er ikke implicit kontravariant, men det kan de være udtrykkeligt modstridende , ligesom generiske typer. Jeg vil tilbyde nogle eksempler senere i artiklen.

Typeafhængige elementer: Metoder og typer

I Java er metoder, matrixtyper og generiske (parametriserede) typer de typeafhængige elementer. Metoder er afhængige af typerne af deres parametre. En array-type, T [], er afhængig af typerne af dets elementer, T. En generisk type G er afhængig af dens typeparameter, T. Figur 5 illustrerer.

Andreas Solymosi

For det meste fokuserer denne artikel på typekompatibilitet, selvom jeg vil berøre kompatibilitet blandt metoder mod slutningen af ​​del 2.

Implicit og eksplicit typekompatibilitet

Tidligere så du typen T1 være implicit (eller eksplicit) kompatibel med T2. Dette gælder kun, hvis tildelingen af ​​en variabel af typen T1 til en variabel af typen T2 er tilladt uden (eller med) tagging. Type casting er den hyppigste måde at tagge eksplicit kompatibilitet på:

 variabelOfTypeT2 = variabelOfTypeT1; // implicit kompatibel variabelOfTypeT2 = (T2) variabelOfTypeT1; // eksplicit kompatibel 

For eksempel, int er implicit kompatibel med lang og eksplicit kompatibel med kort:

 int intVariable = 5; lang longVariable = intVariable; // implicit kompatibel kort shortVariable = (kort) intVariable; // eksplicit kompatibel 

Implicit og eksplicit kompatibilitet eksisterer ikke kun i opgaver, men også ved videregivelse af parametre fra et metodekald til en metodedefinition og tilbage. Sammen med inputparametre betyder det også at sende et funktionsresultat, som du ville gøre som en outputparameter.

Noter det boolsk er ikke kompatibel med nogen anden type, og en primitiv og en referencetype kan heller ikke være kompatibel.

Metodeparametre

Vi siger, en metode læser inputparametre og skriver outputparametre. Parametre for primitive typer er altid inputparametre. En funktions returværdi er altid en outputparameter. Parametre for referencetyper kan være begge: Hvis metoden ændrer referencen (eller en primitiv parameter), forbliver ændringen inden for metoden (hvilket betyder, at den ikke er synlig uden for metoden efter opkaldet - dette er kendt som kald efter værdi). Hvis metoden ændrer det henviste objekt, forbliver ændringen dog, efter at den er returneret fra metoden - dette er kendt som kald ved reference.

En (reference) undertype er implicit kompatibel med dens supertype, og en supertype er eksplicit kompatibel med dens subtype. Dette betyder, at referencetyper kun er kompatible inden for deres hierarkiforgrening - opad implicit og nedad eksplicit:

 referenceOfSuperType = referenceOfSubType; // implicit kompatibel referenceOfSubType = (SubType) referenceOfSuperType; // eksplicit kompatibel 

Java-kompilatoren tillader typisk implicit kompatibilitet for en opgave kun hvis der ikke er nogen fare for at miste information ved kørsel mellem de forskellige typer. (Bemærk dog, at denne regel ikke er gyldig for at miste præcision, f.eks. I en opgave fra int at flyde.) For eksempel int er implicit kompatibel med lang fordi en lang variabel holder hver int værdi. I modsætning hertil a kort variabel indeholder ikke nogen int værdier; således er kun eksplicit kompatibilitet tilladt mellem disse elementer.

Andreas Solymosi

Bemærk, at den implicitte kompatibilitet i figur 6 forudsætter, at forholdet er transitiv: kort er kompatibel med lang.

Svarende til hvad du ser i figur 6, er det altid muligt at tildele en reference til en undertype int en reference til en supertype. Husk, at den samme opgave i den anden retning kan kaste en ClassCastExceptiondog, så Java-kompilatoren tillader det kun med type casting.

Kovarians og kontrast til matrixtyper

I Java er nogle array-typer kovariante og / eller kontravariant. I tilfælde af kovarians betyder det, at hvis T er kompatibel med U, derefter T [] er også kompatibel med U []. I tilfælde af modstrid betyder det U [] er kompatibel med T []. Arrays af primitive typer er uændrede i Java:

 longArray = intArray; // typefejl shortArray = (kort []) intArray; // typefejl 

Arrays af referencetyper er implicit kovariant og udtrykkeligt modstridendedog:

 SuperType [] superArray; Undertype [] subArray; ... superArray = subArray; // implicit covariant subArray = (SubType []) superArray; // eksplicit modstridende 
Andreas Solymosi

Figur 7. Implicit kovarians for arrays

Hvad dette praktisk betyder, er, at en opgave af array-komponenter kunne kaste ArrayStoreException ved kørselstid. Hvis en matrixreference for SuperType henviser til et array-objekt af Undertype, og en af ​​dens komponenter tildeles derefter til a SuperType objekt, så:

 superArray [1] = ny SuperType (); // kaster ArrayStoreException 

Dette kaldes undertiden kovariansproblem. Det sande problem er ikke så meget undtagelsen (som kunne undgås ved programmeringsdisciplin), men at den virtuelle maskine skal kontrollere hver opgave i et array-element ved kørsel. Dette sætter Java i en effektiv ulempe i forhold til sprog uden kovarians (hvor en kompatibel opgave til matrixreferencer er forbudt) eller sprog som Scala, hvor kovarians kan slås fra.

Et eksempel på kovarians

I et simpelt eksempel er matrixreferencen af ​​typen Objekt[] men arrayobjektet og elementerne er af forskellige klasser:

 Objekt [] objectArray; // array reference objectArray = ny streng [3]; // array-objekt; kompatibel tildelingsobjektArray [0] = nyt heltal (5); // kaster ArrayStoreException 

På grund af kovarians kan kompilatoren ikke kontrollere rigtigheden af ​​den sidste tildeling til arrayelementerne - JVM gør dette og med betydelige omkostninger. Imidlertid kan compileren optimere udgiften væk, hvis der ikke er brug af typekompatibilitet mellem arraytyper.

Andreas Solymosi

Husk, at det i Java er forbudt for en referencevariabel af en eller anden type, der henviser til et objekt af dets supertype: pilene i figur 8 må ikke rettes opad.

Afvigelser og jokertegn i generiske typer

Generiske (parametriserede) typer er implicit invariant i Java, hvilket betyder, at forskellige instantieringer af en generisk type ikke er kompatible indbyrdes. Selv type casting vil ikke resultere i kompatibilitet:

 Generisk superGenerisk; Generisk subGenerisk; subGeneric = (Generisk) superGeneric; // typefejl superGeneric = (Generisk) subGeneric; // typefejl 

Typefejlene opstår selvom subGeneric.getClass () == superGeneric.getClass (). Problemet er, at metoden getClass () bestemmer den rå type - dette er grunden til, at en type parameter ikke hører til signaturen for en metode. Således er de to metodedeklarationer

 ugyldig metode (generisk p); ugyldig metode (generisk p); 

må ikke forekomme sammen i en interface (eller abstrakt klasse) definition.