Programmering

Sådan bruges Java generics for at undgå ClassCastExceptions

Java 5 bragte generics til Java-sproget. I denne artikel introducerer jeg dig til generiske stoffer og diskuterer generiske typer, generiske metoder, generiske stoffer og type inferens, generiske kontroverser og generiske og bunkeforurening.

download Hent koden Download kildekoden for eksempler i denne Java 101-tutorial. Oprettet af Jeff Friesen til JavaWorld.

Hvad er generiske lægemidler?

Generiske stoffer er en samling af relaterede sprogfunktioner, der gør det muligt for typer eller metoder at arbejde på objekter af forskellige typer, samtidig med at de giver kompileringssikkerhed. Generiske funktioner løser problemet med java.lang.ClassCastExceptions kastes ved kørsel, hvilket er resultatet af kode, der ikke er typesikker (dvs. at kaste objekter fra deres nuværende typer til inkompatible typer).

Generics og Java Collections Framework

Generics bruges i vid udstrækning i Java Collections Framework (formelt introduceret i fremtiden Java 101 artikler), men de er ikke eksklusive for det. Generics bruges også i andre dele af Java's standardklassebibliotek, herunder java.lang.Klasse, java.lang. sammenlignelig, java.lang.ThreadLocalog java.lang.ref.WeakReference.

Overvej følgende kodefragment, som demonstrerer manglen på typesikkerhed (i sammenhæng med Java Collections Framework's java.util.LinkedList klasse), der var almindelig i Java-kode, før generics blev introduceret:

Liste doubleList = ny LinkedList (); doubleList.add (ny Double (3.5)); Dobbelt d = (Dobbelt) doubleList.iterator (). Næste ();

Selvom målet med ovenstående program kun er at gemme java.lang. dobbelt objekter på listen, intet forhindrer andre slags objekter i at blive gemt. For eksempel kan du specificere doubleList.add ("Hej"); at tilføje en java.lang.Streng objekt. Når du gemmer en anden form for objekt, den sidste linje (Dobbelt) cast operatør årsager ClassCastException at blive kastet, når de konfronteres med en ikke-Dobbelt objekt.

Da denne mangel på typesikkerhed ikke registreres før runtime, er en udvikler muligvis ikke opmærksom på problemet og overlader det til klienten (i stedet for compileren) at opdage. Generics hjælper compileren med at advare udvikleren om problemet med at gemme et objekt med et ikke-Dobbelt skriv listen ved at lade udvikleren markere listen som kun indeholdende Dobbelt genstande. Denne hjælp demonstreres nedenfor:

Liste doubleList = ny LinkedList (); doubleList.add (ny Double (3.5)); Dobbelt d = doubleList.iterator (). Næste ();

Liste læser nu “Liste af Dobbelt.” Liste er en generisk grænseflade, udtrykt som Liste, det tager en Dobbelt type argument, som også angives, når du opretter det aktuelle objekt. Compileren kan nu håndhæve typekorrekthed, når han tilføjer et objekt til listen - for eksempel kunne listen gemme Dobbelt kun værdier. Denne håndhævelse fjerner behovet for (Dobbelt) støbt.

Opdage generiske typer

EN generisk type er en klasse eller grænseflade, der introducerer et sæt parametriserede typer via en formel liste over parametre, som er en komma-adskilt liste over typeparameternavne mellem et par vinkelparenteser. Generiske typer overholder følgende syntaks:

klasse identifikator<formalTypeParameterList> {// class body} interface identifikator<formalTypeParameterList> {// interface-krop}

Java Collections Framework tilbyder mange eksempler på generiske typer og deres parameterlister (og jeg henviser til dem i hele denne artikel). For eksempel, java.util.Set er en generisk type, er dens formelle parameterliste, og E er listeens ensomme parameter. Et andet eksempel erjava.util.Kort.

Java-typeparameter navngivningskonvention

Java-programmeringskonvention dikterer, at typeparameternavne er enkelt store bogstaver, f.eks E for element, K for nøgle, V for værdi og T for type. Hvis det er muligt, skal du undgå at bruge et meningsløst navn som Pjava.util.Liste betyder en liste med elementer, men hvad kan du muligvis mene med Liste

EN parametreret type er en generisk type forekomst, hvor den generiske type's typeparametre erstattes med faktiske type argumenter (typenavne). For eksempel, Sæt er en parametreret type, hvor Snor er det aktuelle argumentargument, der erstatter typeparameteren E.

Java-sproget understøtter følgende slags faktiske argumentargumenter:

  • Beton type: En klasse eller et andet referencetypenavn sendes til typeparameteren. For eksempel i Liste, Dyr overføres til E.
  • Konkret parametreret type: Et parameteriseret typenavn sendes til typeparameteren. For eksempel i Sæt, Liste overføres til E.
  • Array type: Et array sendes til typeparameteren. For eksempel i Kort, Snor overføres til K og Snor[] overføres til V.
  • Type parameter: En typeparameter videregives til typeparameteren. For eksempel i klasse Container {Sæt elementer; }, E overføres til E.
  • Jokertegn: Spørgsmålstegnet (?) overføres til typeparameteren. For eksempel i Klasse, ? overføres til T.

Hver generisk type indebærer eksistensen af ​​en rå type, som er en generisk type uden en formel typeparameterliste. For eksempel, Klasse er den rå type til Klasse. I modsætning til generiske typer kan rå typer bruges med enhver form for objekt.

Erklæring og brug af generiske typer i Java

At erklære en generisk type indebærer at angive en formel typeparameterliste og få adgang til disse typeparametre under hele implementeringen. Brug af den generiske type indebærer at videregive faktiske typeargumenter til dens typeparametre, når man genererer den generiske type. Se liste 1.

Liste 1:GenDemo.java (version 1)

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

Fortegnelse 1 viser generisk typedeklaration og anvendelse i sammenhæng med en simpel beholdertype, der gemmer objekter af den relevante argumenttype. For at holde koden enkel har jeg udeladt fejlkontrol.

Det Beholder klasse erklærer sig for at være en generisk type ved at specificere formel parameterparameterliste. Type parameter E bruges til at identificere typen af ​​lagrede elementer, det element, der skal føjes til det interne array, og returtypen, når et element hentes.

Det Beholder (int størrelse) konstruktør opretter arrayet via elementer = (E []) nyt objekt [størrelse];. Hvis du undrer dig over, hvorfor jeg ikke specificerede det elementer = ny E [størrelse];, årsagen er, at det ikke er muligt. Dette kan føre til en ClassCastException.

Kompilere lister 1 (javac GenDemo.java). Det (E []) cast får compileren til at udsende en advarsel om, at casten ikke er markeret. Det markerer muligheden for, at downcasting fra Objekt[] til E [] kan krænke typesikkerhed, fordi Objekt[] kan gemme alle typer objekter.

Bemærk dog, at der ikke er nogen måde at krænke typesikkerheden i dette eksempel. Det er simpelthen ikke muligt at gemme en ikke-E objekt i det interne array. Prefixing af Beholder (int størrelse) konstruktør med @SuppressWarnings ("ikke markeret") ville undertrykke denne advarselsmeddelelse.

Udfør java GenDemo for at køre denne applikation. Du skal overholde følgende output:

nord syd øst vest

Parametre for afgrænsningstype i Java

Det E i Sæt er et eksempel på en ubegrænset parameter fordi du kan videregive ethvert faktisk argument til E. For eksempel kan du angive Sæt, Sæt, eller Sæt.

Nogle gange vil du gerne begrænse de typer af faktiske typeargumenter, der kan overføres til en typeparameter. For eksempel vil du måske begrænse en typeparameter til kun at blive accepteret Medarbejder og dens underklasser.

Du kan begrænse en typeparameter ved at angive en øvre grænse, som er en type, der fungerer som den øvre grænse for de typer, der kan sendes som faktiske typeargumenter. Angiv den øvre grænse ved hjælp af det reserverede ord strækker sig efterfulgt af den øvre grænses type navn.

For eksempel, klasse medarbejdere begrænser de typer, der kan overføres til Medarbejdere til Medarbejder eller en underklasse (f.eks. Revisor). Specificering nye medarbejdere ville være lovligt, mens nye medarbejdere ville være ulovligt.

Du kan tildele mere end en øvre grænse til en typeparameter. Den første grænse skal dog altid være en klasse, og de ekstra grænser skal altid være grænseflader. Hver grænse er adskilt fra sin forgænger med et ampersand (&). Tjek Listing 2.

Liste 2: GenDemo.java (version 2)

import java.math.BigDecimal; importere java.util.Arrays; abstrakt klasse Medarbejder {privat BigDecimal timeløn; privat strengnavn; Medarbejder (String name, BigDecimal hourlySalary) {this.name = name; this.hourlySalary = hourlySalary; } offentlig BigDecimal getHourlySalary () {return hourlySalary; } public String getName () {return name; } offentlig String toString () {return name + ":" + hourlySalary.toString (); }} klasse Revisor udvider Medarbejderimplementer Sammenlignelig {Accountant (String name, BigDecimal hourlySalary) {super (name, hourlySalary); } public int CompareTo (Accountant acct) {return getHourlySalary (). CompareTo (acct.getHourlySalary ()); }} klasse SortedEmployees {private E [] medarbejdere privat int-indeks; @SuppressWarnings ("ikke markeret") SortedMedarbejdere (int størrelse) {medarbejdere = (E []) ny medarbejder [størrelse]; int-indeks = 0; } ugyldig tilføj (E emp) {medarbejdere [index ++] = emp; Arrays.sort (medarbejdere, 0, indeks); } Få (int-indeks) {returnere medarbejdere [indeks]; } int størrelse () {returindeks; }} offentlig klasse GenDemo {public static void main (String [] args) {SortedEmployees se = new SortedEmployees (10); se.add (ny bogholder ("John Doe", ny BigDecimal ("35.40"))); se.add (ny revisor ("George Smith", ny BigDecimal ("15.20")); se.add (ny bogholder ("Jane Jones", ny BigDecimal ("25,60")); for (int i = 0; i <se.størrelse (); i ++) System.out.println (se.get (i)); }}

Liste 2's Medarbejder klasse abstraherer begrebet medarbejder, der modtager en timeløn. Denne klasse er underklasseret af Revisor, som også implementerer Sammenlignelig for at angive det Revisors kan sammenlignes i henhold til deres naturlige rækkefølge, hvilket tilfældigvis er timeløn i dette eksempel.

Det java.lang. sammenlignelig interface er erklæret som en generisk type med en enkelt typeparameter navngivet T. Denne grænseflade giver en int CompareTo (To) metode, der sammenligner det aktuelle objekt med argumentet (af typen T), returnerer et negativt heltal, nul eller et positivt heltal, da dette objekt er mindre end, lig med eller større end det angivne objekt.

Det SortedMedarbejdere klasse kan du gemme Medarbejder underklasseforekomster, der implementeres Sammenlignelig i et internt array. Denne matrix er sorteret (via java.util.Arrays klasse ugyldig sortering (Objekt [] a, int fraIndex, int tilIndex) klassemetode) i stigende rækkefølge efter timelønnen efter en Medarbejder subklasse-forekomst tilføjes.

Kompilere lister 2 (javac GenDemo.java) og kør applikationen (java GenDemo). Du skal overholde følgende output:

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

Nedre grænser og generiske parametre

Du kan ikke angive en nedre grænse for en generisk typeparameter. For at forstå hvorfor jeg anbefaler at læse Angelika Langers ofte stillede spørgsmål om Java Generics om emnet med lavere grænser, som hun siger "ville være forvirrende og ikke særlig nyttigt."

I betragtning af jokertegn

Lad os sige, at du vil udskrive en liste over objekter, uanset om disse objekter er strenge, medarbejdere, figurer eller en anden type. Dit første forsøg kan se ud som det, der vises i Listing 3.

Liste 3: GenDemo.java (version 3)

importere java.util.ArrayList; importere java.util.Iterator; importere java.util.List; public class GenDemo {public static void main (String [] args) {Listevejledning = ny ArrayList (); retninger.add ("nord"); retninger.add ("syd"); retninger.add ("øst"); retninger.add ("vest"); printList (retninger); Listekarakterer = ny ArrayList (); karakterer.add (nyt heltal (98)); karakterer.add (nyt heltal (63)); karakterer.add (nyt 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 strenge eller en liste over heltal er en undertype af en liste over objekter, men kompilatoren klager, når du forsøger at kompilere denne liste. Specifikt fortæller det dig, at en liste-af-streng ikke kan konverteres til en liste-over-objekt og på samme måde for en liste over heltal.

Den fejlmeddelelse, du har modtaget, er relateret til den grundlæggende regel for generiske stoffer:

Copyright verticalshadows.com 2021