Programmering

Skriv avhengighet i Java, del 1

Å forstå typekompatibilitet er grunnleggende for å skrive gode Java-programmer, men samspillet mellom avvik mellom Java-språkelementer kan virke veldig akademisk for uinnvidde. Denne artikkelen er for programvareutviklere som er klare til å takle utfordringen! Del 1 avslører de kovariante og kontravariante forholdene mellom enklere elementer som matrixtyper og generiske typer, så vel som det spesielle Java-språkelementet, jokertegnet. Del 2 utforsker typeavhengighet og varians i vanlige API-eksempler og i lambdauttrykk.

last ned Last ned kilden Få kildekoden for denne artikkelen "Type avhengighet i Java, del 1." Laget for JavaWorld av Dr. Andreas Solymosi.

Konsepter og terminologi

Før vi går inn i forholdet mellom kovarians og kontravarans mellom ulike Java-språkelementer, la oss være sikre på at vi har et felles konseptuelt rammeverk.

Kompatibilitet

I objektorientert programmering, kompatibilitet refererer til en rettet sammenheng mellom typene, som vist i figur 1.

Andreas Solymosi

Vi sier at to typer er kompatibel i Java hvis det er mulig å overføre data mellom variablene av typene. Dataoverføring er mulig hvis kompilatoren godtar det, og gjøres gjennom tildeling eller parameteroverføring. Som et eksempel, kort er kompatibel med int fordi oppdraget intVariable = shortVariable; er mulig. Men boolsk er ikke kompatibel med int fordi oppdraget intVariable = booleanVariable; det er ikke mulig; kompilatoren godtar ikke det.

Fordi kompatibilitet er et direkte forhold, noen ganger T1 er kompatibel med T2 men T2 er ikke kompatibel med T1, eller ikke på samme måte. Vi får se dette nærmere når vi kommer til å diskutere eksplisitt eller implisitt kompatibilitet.

Det som betyr noe er at kompatibilitet mellom referansetyper er mulig kun innenfor et typehierarki. Alle klassetyper er kompatible med Gjenstand, for eksempel fordi alle klassene arver implisitt fra Gjenstand. Heltall er ikke kompatibel med Flyteimidlertid fordi Flyte er ikke en superklasse av Heltall. Heltaller kompatibel med Nummer, fordi Nummer er en (abstrakt) superklasse av Heltall. Fordi de er plassert i samme type hierarki, godtar kompilatoren oppgaven numberReference = heltallReferanse;.

Vi snakker om implisitt eller eksplisitt kompatibilitet, avhengig av om kompatibilitet må merkes eksplisitt eller ikke. For eksempel er kort implisitt kompatibel med int (som vist ovenfor), men ikke omvendt: oppgaven shortVariable = intVariable; det er ikke mulig. Imidlertid er kort eksplisitt kompatibel med int, fordi oppgaven shortVariable = (kort) intVariable; er mulig. Her må vi merke kompatibilitet med avstøpning, også kjent som typekonvertering.

Tilsvarende blant referansetyper: integerReference = numberReference; er ikke akseptabelt, bare integerReference = (Heltall) numberReference; ville bli akseptert. Derfor, Heltall er implisitt kompatibel med Nummer men Nummer er bare eksplisitt kompatibel med Heltall.

Avhengighet

En type kan avhenge av andre typer. For eksempel arraytypen int [] avhenger av den primitive typen int. Tilsvarende den generiske typen ArrayList er avhengig av typen Kunde. Metoder kan også være typeavhengige, avhengig av parametertypene. For eksempel metoden ugyldig økning (heltall i); avhenger av typen Heltall. Noen metoder (som noen generiske typer) avhenger av mer enn én type - for eksempel metoder som har mer enn én parameter.

Kovarians og kontravarians

Kovarians og kontravarans bestemmer kompatibilitet basert på typer. I begge tilfeller er varians en rettet relasjon. Kovarians kan oversettes som "forskjellige i samme retning," eller med-annerledes, mens kontravarians betyr "forskjellig i motsatt retning," eller mot-annerledes. Kovariant og kontravariant er ikke det samme, men det er en sammenheng mellom dem. Navnene antyder retningen på korrelasjonen.

Så, kovarians betyr at kompatibiliteten til to typer innebærer kompatibiliteten til typene som er avhengige av dem. Gitt typekompatibilitet, antar man at avhengige typer er kovariante, som vist i figur 2.

Andreas Solymosi

Kompatibiliteten til T1 til T2 innebærer kompatibiliteten til 1) til 2). Den avhengige typen PÅ) er kalt kovariant; eller mer presist, 1) er kovariant til 2).

For et annet eksempel: fordi oppgaven numberArray = heltallArray; er mulig (i det minste i Java), arraytypene Heltall [] og Nummer[] er kovariante. Så vi kan si det Heltall [] er implisitt kovariant til Nummer[]. Og mens det motsatte ikke er sant - oppgaven integerArray = numberArray; er ikke mulig - oppgaven med type støping (integerArray = (Heltall []) numberArray;) er mulig; derfor sier vi, Nummer[] er eksplisitt kovariant til Heltall [] .

Å oppsummere: Heltall er implisitt kompatibel med Nummer, derfor Heltall [] er implisitt kovariant til Nummer[], og Nummer[] er eksplisitt kovariant til Heltall [] . Figur 3 illustrerer.

Andreas Solymosi

Generelt sett kan vi si at matrityper er samvariante i Java. Vi vil se på eksempler på kovarians blant generiske typer senere i artikkelen.

Kontravaranse

I likhet med kovarians er kontravarians en regissert forhold. Mens kovarians betyr med-annerledes, kontravarians betyr mot-annerledes. Som jeg tidligere nevnte, navnene uttrykker retningen på korrelasjonen. Det er også viktig å merke seg at varians ikke er et attributt av typer generelt, men bare av avhengig typer (som matriser og generiske typer, og også av metoder, som jeg vil diskutere i del 2).

En avhengig type som PÅ) er kalt motstridende hvis kompatibiliteten til T1 til T2 innebærer kompatibiliteten til 2) til 1). Figur 4 illustrerer.

Andreas Solymosi

Et språkelement (type eller metode) PÅ) avhengig av T er kovariant hvis kompatibiliteten til T1 til T2 innebærer kompatibiliteten til 1) til 2). Hvis kompatibiliteten til T1 til T2 innebærer kompatibiliteten til 2) til 1), deretter typen PÅ) er motstridende. Hvis kompatibiliteten til T1 mellom T2 innebærer ingen kompatibilitet mellom 1) og 2), deretter PÅ) er invariant.

Array-typer i Java er ikke det implisitt motstridende, men det kan de være eksplisitt motstridende , akkurat som generiske typer. Jeg vil gi noen eksempler senere i artikkelen.

Typeavhengige elementer: Metoder og typer

I Java er metoder, arraytyper og generiske (parametriserte) typer de typeavhengige elementene. Metodene er avhengig av parametertypene. En array-type, T [], er avhengig av typene av elementene, T. En generisk type G er avhengig av type parameter, T. Figur 5 illustrerer.

Andreas Solymosi

For det meste fokuserer denne artikkelen på typekompatibilitet, selv om jeg vil berøre kompatibilitet mellom metoder mot slutten av del 2.

Implisitt og eksplisitt typekompatibilitet

Tidligere så du typen T1 å være implisitt (eller eksplisitt) kompatibel med T2. Dette gjelder bare hvis tildelingen av en variabel av typen T1 til en variabel av typen T2 er tillatt uten (eller med) merking. Type casting er den hyppigste måten å merke eksplisitt kompatibilitet:

 variableOfTypeT2 = variableOfTypeT1; // implisitt kompatibel variabelOfTypeT2 = (T2) variabelOfTypeT1; // eksplisitt kompatibel 

For eksempel, int er implisitt kompatibel med lang og eksplisitt kompatibel med kort:

 int intVariable = 5; lang langVariabel = intVariabel; // implisitt kompatibel kort shortVariable = (kort) intVariable; // eksplisitt kompatibel 

Implisitt og eksplisitt kompatibilitet eksisterer ikke bare i oppgaver, men også i overføring av parametere fra et metodeanrop til en metodedefinisjon og tilbake. Sammen med inngangsparametere betyr dette også å sende et funksjonsresultat, som du ville gjort som en utgangsparameter.

Noter det boolsk er ikke kompatibel med noen annen type, og en primitiv og en referansetype kan heller ikke være kompatibel.

Metodeparametere

Vi sier, en metode leser inngangsparametere og skriver utgangsparametere. Parametere for primitive typer er alltid inngangsparametere. En returverdi for en funksjon er alltid en utgangsparameter. Parametere for referansetyper kan være begge deler: Hvis metoden endrer referansen (eller en primitiv parameter), forblir endringen innenfor metoden (noe som betyr at den ikke er synlig utenfor metoden etter samtalen - dette er kjent som ring etter verdi). Hvis metoden endrer det henviste objektet, forblir endringen etter at den er returnert fra metoden - dette er kjent som ring ved referanse.

En (referanse) undertype er implisitt kompatibel med supertypen, og en supertype er eksplisitt kompatibel med subtypen. Dette betyr at referansetyper bare er kompatible innenfor deres hierarkiforgrening - opp implisitt og nedover eksplisitt:

 referenceOfSuperType = referenceOfSubType; // implisitt kompatibel referenceOfSubType = (SubType) referenceOfSuperType; // eksplisitt kompatibel 

Java-kompilatoren tillater vanligvis implisitt kompatibilitet for en oppgave kun hvis det ikke er fare for å miste informasjon på kjøretid mellom de forskjellige typene. (Vær imidlertid oppmerksom på at denne regelen ikke er gyldig for å miste presisjon, for eksempel i en oppgave fra int å flyte.) For eksempel int er implisitt kompatibel med lang fordi en lang variabel holder hver int verdi. I kontrast, a kort variabel inneholder ikke noe int verdier; dermed er bare eksplisitt kompatibilitet tillatt mellom disse elementene.

Andreas Solymosi

Merk at den implisitte kompatibiliteten i figur 6 forutsetter at forholdet er transitive: kort er kompatibel med lang.

I likhet med det du ser i figur 6, er det alltid mulig å tildele en referanse til en undertype int en referanse til en supertype. Husk at den samme oppgaven i den andre retningen kan kaste a ClassCastExceptionimidlertid, slik at Java-kompilatoren bare tillater det med type casting.

Kovarians og kontravarians for matrityper

I Java er noen matrityper kovariante og / eller kontravariant. Når det gjelder samvarians, betyr dette at hvis T er kompatibel med U, deretter T [] er også kompatibel med U []. I tilfelle kontravaranse betyr det at U [] er kompatibel med T []. Matriser av primitive typer er uforanderlige i Java:

 longArray = intArray; // skriv feil shortArray = (kort []) intArray; // typefeil 

Arrangementer av referansetyper er implisitt kovariant og eksplisitt motstridende, derimot:

 SuperType [] superArray; SubType [] subArray; ... superArray = subArray; // implisitt kovariant subArray = (SubType []) superArray; // eksplisitt motstridende 
Andreas Solymosi

Figur 7. Implisitt kovarians for matriser

Hva dette egentlig betyr, er at en oppgave av array-komponenter kan kaste ArrayStoreException ved kjøretid. Hvis en matrisereferanse av Supertype refererer til et arrayobjekt av Undertype, og en av komponentene tildeles deretter til a Supertype objekt, deretter:

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

Dette kalles noen ganger kovariansproblem. Det sanne problemet er ikke så mye unntaket (som kan unngås ved programmeringsdisiplin), men at den virtuelle maskinen må sjekke hvert oppdrag i et array-element ved kjøretid. Dette setter Java i en effektiv ulempe mot språk uten kovarians (der en kompatibel oppgave for matrisereferanser er forbudt) eller språk som Scala, der kovarians kan slås av.

Et eksempel på kovarians

I et enkelt eksempel er matrisereferansen av type Gjenstand[] men arrayobjektet og elementene er av forskjellige klasser:

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

På grunn av kovarians kan ikke kompilatoren kontrollere korrektheten til den siste tildelingen til matriseelementene - JVM gjør dette, og til betydelig kostnad. Imidlertid kan kompilatoren optimalisere utgiftene, hvis det ikke er bruk av typekompatibilitet mellom matrityper.

Andreas Solymosi

Husk at i Java er det forbudt for en referansevariabel av en eller annen type som refererer til et objekt av supertypen: pilene i figur 8 må ikke rettes oppover.

Avvik og jokertegn i generiske typer

Generiske (parametriserte) typer er implisitt invariant i Java, noe som betyr at forskjellige instantiasjoner av generisk type ikke er kompatible mellom hverandre. Selv støping av typen vil ikke resultere i kompatibilitet:

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

Typefeilene oppstår selv om subGeneric.getClass () == superGeneric.getClass (). Problemet er at metoden getClass () bestemmer rå typen - dette er grunnen til at en type parameter ikke hører til signaturen til en metode. Dermed de to metodedeklarasjonene

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

må ikke forekomme sammen i en grensesnitt (eller abstrakt klasse) definisjon.

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