Programmering

Hvordan bruke Java generics for å unngå ClassCastExceptions

Java 5 brakte generikk til Java-språket. I denne artikkelen introduserer jeg deg for generiske legemidler og diskuterer generiske typer, generiske metoder, generiske stoffer og type inferens, generiske kontroverser, og generiske og haugeforurensning.

last ned Få koden Last ned kildekoden for eksempler i denne Java 101-opplæringen. Skapt av Jeff Friesen for JavaWorld.

Hva er generiske legemidler?

Generiske er en samling av relaterte språkfunksjoner som gjør det mulig for typer eller metoder å operere på objekter av forskjellige typer, samtidig som de gir kompileringssikkerhet. Generiske funksjoner løser problemet med java.lang.ClassCastExceptionblir kastet ved kjøretid, som er et resultat av kode som ikke er typesikker (dvs. å kaste gjenstander fra deres nåværende typer til inkompatible typer).

Generics og Java Collections Framework

Generiske stoffer er mye brukt i Java Collections Framework (formelt introdusert i fremtiden Java 101 artikler), men de er ikke eksklusive for det. Generiske er også brukt i andre deler av Java standard klassebibliotek inkludert java.lang.Klasse, java.lang. sammenlignbar, java.lang.ThreadLocal, og java.lang.ref.WeakReference.

Tenk på følgende kodefragment, som demonstrerer mangelen på typesikkerhet (i sammenheng med Java Collections Framework’s java.util.LinkedList klasse) som var vanlig i Java-kode før generikk ble introdusert:

Liste doubleList = ny LinkedList (); doubleList.add (ny Double (3.5)); Dobbelt d = (Dobbelt) doubleList.iterator (). Neste ();

Selv om målet med programmet ovenfor er å lagre bare java.lang. dobbelt objekter i listen, hindrer ingenting andre typer gjenstander fra å bli lagret. For eksempel kan du spesifisere doubleList.add ("Hei"); for å legge til en java.lang.Streng gjenstand. Men når du lagrer en annen type gjenstand, den endelige linjen (Dobbelt) cast operatør årsaker ClassCastException å bli kastet når de konfronteres med en ikke-Dobbelt gjenstand.

Fordi denne mangelen på typesikkerhet ikke blir oppdaget før kjøretid, kan en utvikler kanskje ikke være klar over problemet, og overlate det til klienten (i stedet for kompilatoren) å oppdage. Generics hjelper kompilatoren med å varsle utvikleren om problemet med å lagre et objekt med et ikke-Dobbelt skriv inn listen ved å tillate utvikleren å merke listen som bare inneholder Dobbelt gjenstander. Denne hjelpen er vist nedenfor:

Liste doubleList = ny LinkedList (); doubleList.add (ny Double (3.5)); Dobbelt d = doubleList.iterator (). Neste ();

Liste lyder nå “Liste av Dobbelt.” Liste er et generisk grensesnitt, uttrykt som Liste, det tar en Dobbelt type argument, som også spesifiseres når du oppretter det faktiske objektet. Kompilatoren kan nå håndheve typekorrekthet når du legger til et objekt i listen - for eksempel kan listen lagres Dobbelt bare verdier. Denne håndhevelsen fjerner behovet for (Dobbelt) rollebesetning.

Oppdage generiske typer

EN generisk type er en klasse eller et grensesnitt som introduserer et sett med parametrerte typer via en formell type parameterliste, som er en kommaseparert liste over typeparameternavn mellom et par vinkelparenteser. Generiske typer følger følgende syntaks:

klasse identifikator<formalTypeParameterList> {// class body} -grensesnitt identifikator<formalTypeParameterList> {// grensesnitttekst}

Java Collections Framework tilbyr mange eksempler på generiske typer og deres parameterlister (og jeg henviser til dem gjennom denne artikkelen). For eksempel, java.util.Sett er en generisk type, er dens formelle parameterliste, og E er listens ensomme parameter. Et annet eksempel erjava.util.Kart.

Navnekonvensjon for Java-typeparameter

Java-programmeringskonvensjon dikterer at typeparameternavn er enkle store bokstaver, for eksempel E for element, K for nøkkel, V for verdi, og T for type. Unngå om mulig å bruke et meningsløst navn som Pjava.util.Liste betyr en liste over elementer, men hva kan du muligens mene med Liste

EN parameterisert type er en generisk type forekomst der den generiske typen typeparametere erstattes med faktiske type argumenter (skriv navn). For eksempel, Sett er en parameterisert type hvor String er det faktiske typeargumentet som erstatter typeparameteren E.

Java-språket støtter følgende typer faktiske argumentargumenter:

  • Betong type: En klasse eller et annet referansetypenavn sendes til typeparameteren. For eksempel i Liste, Dyr blir overført til E.
  • Betongparameterisert type: Et parameterisert typenavn sendes til typeparameteren. For eksempel i Sett, Liste blir overført til E.
  • Array type: En matrise overføres til typeparameteren. For eksempel i Kart, String blir overført til K og Streng [] blir overført til V.
  • Type parameter: En typeparameter sendes til typeparameteren. For eksempel i klasse Container {Settelementer; }, E blir overført til E.
  • Jokertegn: Spørsmålstegnet (?) overføres til typeparameteren. For eksempel i Klasse, ? blir overført til T.

Hver generiske type innebærer eksistensen av en rå type, som er en generisk type uten en formell typeparameterliste. For eksempel, Klasse er den rå typen for Klasse. I motsetning til generiske typer, kan rå typer brukes med alle slags objekter.

Deklarere og bruke generiske typer i Java

Å erklære en generisk type innebærer å spesifisere en formell typeparameterliste og få tilgang til disse typeparametrene gjennom hele implementeringen. Bruk av den generiske typen innebærer å overføre faktiske typeargumenter til typeparametrene når den generiske typen startes. Se liste 1.

Oppføring 1:GenDemo.java (versjon 1)

klasse Container {private E [] -elementer; privat int indeks; Beholder (int størrelse) {elementer = (E []) nytt objekt [størrelse]; indeks = 0; } void add (E element) {elements [index ++] = element; } Få (int-indeks) {returelementer [indeks]; } int størrelse () {returindeks; }} offentlig klasse GenDemo {public static void main (String [] args) {Container con = new Container (5); con.add ("Nord"); con.add ("Sør"); con.add ("Øst"); con.add ("West"); for (int i = 0; i <con.size (); i ++) System.out.println (con.get (i)); }}

Oppføring 1 viser generell typedeklarasjon og bruk i sammenheng med en enkel containertype som lagrer objekter av riktig argumenttype. For å holde koden enkel har jeg utelatt feilkontroll.

De Container klasse erklærer seg for å være en generisk type ved å spesifisere formell type parameterliste. Type parameter E brukes til å identifisere typen lagrede elementer, elementet som skal legges til den interne matrisen, og returtypen når et element hentes.

De Beholder (int størrelse) konstruktør oppretter matrisen via elementer = (E []) nytt objekt [størrelse];. Hvis du lurer på hvorfor jeg ikke spesifiserte elementer = ny E [størrelse];, årsaken er at det ikke er mulig. Dette kan føre til en ClassCastException.

Kompilere oppføring 1 (javac GenDemo.java). De (E []) cast får kompilatoren til å sende ut en advarsel om at rollebesetningen ikke er merket av. Det markerer muligheten for at nedkasting fra Gjenstand[] til E [] kan bryte med typen sikkerhet fordi Gjenstand[] kan lagre alle typer objekter.

Vær imidlertid oppmerksom på at det ikke er noen måte å bryte typesikkerheten i dette eksemplet. Det er rett og slett ikke mulig å lagre et ikke-E objekt i den interne matrisen. Prefiksering av Beholder (int størrelse) konstruktør med @SuppressWarnings ("ukontrollert") ville undertrykke denne advarselen.

Henrette java GenDemo for å kjøre dette programmet. Du bør følge følgende utdata:

Nord sør øst vest

Parametere for avgrensningstype i Java

De E i Sett er et eksempel på en parameter for ubegrenset type fordi du kan overføre et hvilket som helst faktisk argument til E. For eksempel kan du spesifisere Sett, Sett, eller Sett.

Noen ganger vil du begrense typene av faktiske typeargumenter som kan overføres til en typeparameter. For eksempel, kanskje du vil begrense en typeparameter til å bare akseptere Ansatt og underklassene.

Du kan begrense en typeparameter ved å spesifisere en øvre grense, som er en type som fungerer som den øvre grensen for typene som kan sendes som faktiske typeargumenter. Spesifiser øvre grense ved å bruke det reserverte ordet strekker etterfulgt av navnet på den øvre grensen.

For eksempel, klasse Ansatte begrenser typene som kan overføres til Ansatte til Ansatt eller en underklasse (f.eks. Regnskapsfører). Spesifisering nye ansatte ville være lovlig, mens nye ansatte ville være ulovlig.

Du kan tilordne mer enn én øvre grense til en typeparameter. Imidlertid må den første grensen alltid være en klasse, og de ekstra grensene må alltid være grensesnitt. Hver avgrensning er skilt fra forgjengeren med et ampersand (&). Sjekk ut oppføring 2.

Oppføring 2: GenDemo.java (versjon 2)

importere java.math.BigDecimal; importere java.util.Arrays; abstrakt klasse Ansatt {privat BigDecimal timeslønn; privat strengnavn; Ansatt (strengnavn, BigDecimal timelønn) {this.name = navn; this.hourlySalary = hourlySalary; } offentlig BigDecimal getHourlySalary () {return hourlySalary; } public String getName () {return name; } public String toString () {return name + ":" + hourlySalary.toString (); }} klasse Regnskapsfører utvider Ansatt implementerer Sammenlignbar {Regnskapsfører (String navn, BigDecimal hourlySalary) {super (name, hourlySalary); } public int CompareTo (Accountant acct) {return getHourlySalary (). CompareTo (acct.getHourlySalary ()); }} klasse SortedMedarbeidere {private E [] ansatte; privat int indeks; @SuppressWarnings ("ukontrollert") SortedMedarbeidere (int størrelse) {ansatte = (E []) ny ansatt [størrelse]; int-indeks = 0; } ugyldig legge til (E emp) {ansatte [index ++] = emp; Arrays.sort (ansatte, 0, indeks); } Få (int-indeks) {retur ansatte [indeks]; } int størrelse () {returindeks; }} offentlig klasse GenDemo {public static void main (String [] args) {SortedEmployees se = new SortedEmployees (10); se.add (ny regnskapsfører ("John Doe", nye BigDecimal ("35.40")); se.add (ny regnskapsfører ("George Smith", ny BigDecimal ("15.20")); se.add (ny regnskapsfører ("Jane Jones", nye BigDecimal ("25,60"))); for (int i = 0; i <se.størrelse (); i ++) System.out.println (se.get (i)); }}

Listing 2’s Ansatt klasse trekker ut begrepet arbeidstaker som mottar timelønn. Denne klassen er underklassert av Regnskapsfører, som også implementerer Sammenlignelig for å indikere det Regnskapsførers kan sammenlignes i henhold til deres naturlige rekkefølge, som tilfeldigvis er timelønn i dette eksemplet.

De java.lang. sammenlignbar grensesnitt er erklært som en generisk type med en enkelt type parameter som heter T. Dette grensesnittet gir en int CompareTo (T o) metode som sammenligner gjeldende objekt med argumentet (av typen T), returnerer et negativt heltall, null eller et positivt heltall ettersom dette objektet er mindre enn, lik eller større enn det spesifiserte objektet.

De Sorterte ansatte klasse lar deg lagre Ansatt underklasseforekomster som implementeres Sammenlignelig i en intern matrise. Denne matrisen er sortert (via java.util.Arrayer klasse’s void sort (Object [] a, int fromIndex, int toIndex) klassemetode) i stigende rekkefølge etter timelønnen etter en Ansatt underklasse forekomst er lagt til.

Kompilere oppføring 2 (javac GenDemo.java) og kjør applikasjonen (java GenDemo). Du bør følge følgende utdata:

George Smith: 15.20 Jane Jones: 25,60 John Doe: 35,40

Nedre grenser og generiske parametere

Du kan ikke spesifisere en nedre grense for en parameter for generisk type. For å forstå hvorfor jeg anbefaler å lese vanlige spørsmål om Angelika Langers Java Generics om temaet lavere grenser, som hun sier "ville være forvirrende og ikke spesielt nyttig."

Vurderer jokertegn

La oss si at du vil skrive ut en liste over objekter, uansett om disse objektene er strenger, ansatte, figurer eller en annen type. Ditt første forsøk kan se ut som det som er vist i Listing 3.

Oppføring 3: GenDemo.java (versjon 3)

importere java.util.ArrayList; importere java.util.Iterator; importere java.util.List; offentlig klasse GenDemo {offentlig statisk tomrom hoved (String [] args) {Liste retninger = ny ArrayList (); retninger.add ("nord"); retninger.add ("sør"); retninger.add ("øst"); retninger.add ("vest"); printList (veibeskrivelse); Listekarakterer = ny ArrayList (); karakterer.add (nytt heltal (98)); karakterer.add (nytt heltal (63)); karakterer.add (nytt heltal (87)); printList (karakterer); } statisk ugyldig printList (Liste liste) {Iterator iter = list.iterator (); mens (iter.hasNext ()) System.out.println (iter.next ()); }}

Det virker logisk at en liste over strenger eller en liste over heltall er en undertype av en liste over objekter, men kompilatoren klager når du prøver å kompilere denne oppføringen. Spesielt forteller den deg at en liste-over-streng ikke kan konverteres til en liste-over-objekt, og på samme måte for en liste over heltal.

Feilmeldingen du har mottatt, er relatert til den grunnleggende regelen for generiske legemidler:

Copyright no.verticalshadows.com 2022