Generisk programmering i Java
Generisk programmering i Java erblevet muliggjortaf såkaldte generics siden Java 1.5. Udtrykket er synonymt med “parametriserede typer”. Ideen bag dette er at introducere yderligere variabler for typer. Disse typevariabler repræsenterer ukendte typer på implementeringstidspunktet. Disse typevariabler erstattes kun af konkrete typer, når klasser, grænseflader og metoder anvendes. På denne måde kan typesikker programmering normalt garanteres. Men ikke altid.
Konceptet
Fra version 5.0 ("Tiger", udgivet i 2004), giver Java- programmeringssproget også generiske, et syntaktisk middel til generisk programmering . Dette gør det muligt at parametrere klasser og metoder (metoder også uafhængige af deres klasser) med typer . Dette åbner nogle lignende muligheder for sproget, der kan sammenlignes med skabelonerne i C ++ .
I princippet er der dog væsentlige forskelle. Mens i Java typen parameter parametreres via grænsefladen , i C ++ den art parameter selv er parameteriseret direkte . Kildekoden til en C ++ - skabelon skal være tilgængelig for brugeren (dvs. når typeparameteren indsættes), mens en generisk Java-type også kan offentliggøres som en oversat bytode. Compileren producerer duplikeret målkode til forskellige specifikt anvendte typeparametre.
For eksempel giver funktionen std :: sort i C ++ muligheden for at sortere alle containere, der tilbyder bestemte metoder (her specifikt begynder () og slutter (), som hver returnerer en iterator), og hvis typeparametre implementerer 'operator <' (eller en anden sammenligningsfunktion blev udtrykkeligt specificeret). En ulempe ved dette system er, at det er sværere (for programmøren!) At oversætte. Compileren har ingen anden mulighed end at erstatte typeparameteren i hvert tilfælde med den krævede specifikke type og at kompilere hele koden igen.
I tilfælde af uhensigtsmæssige typeparametre og andre problemer kan der meget let opstå komplicerede og uforståelige compiler-meddelelser, som simpelthen er relateret til det faktum, at de specifikke krav til typeparametrene er ukendte. Arbejde med C ++ skabeloner kræver derfor komplet dokumentation af kravene til en typeparameter. Med skabelon metaprogramming , fleste krav (base klasse, tilgængelighed af metoder, copiability, overdragelighed, etc.) kan også forespørges i specielle konstruktioner, som resulterer i mere læsbare fejlmeddelelser. Selvom de overholder standarderne, understøttes disse konstruktioner ikke af alle compilere .
I modsætning hertil er de generiske klasser og metoder i Java opmærksomme på begrænsningerne for deres egen typeparametre. For at sortere en samling (uden en komparator) skal de elementer, den indeholder, være af den sammenlignelige type, dvs. har implementeret denne grænseflade. Compileren skal kun kontrollere, om typeparameteren er en undertype af Comparable, og kan således sikre, at koden er korrekt (dvs. den krævede metode CompareTo er tilgængelig). Desuden bruges en og samme kode til alle specifikke typer og duplikeres ikke hver gang.
Nyttige eksempler
Et program bruger et til at gemme ArrayListen liste over JButtons.
Indtil videre var ArrayList rettet på typen Objekt :
List list = new ArrayList();
list.add(new JButton("Button 1"));
list.add(new JButton("Button 2"));
list.add(new JButton("Button 3"));
list.add(new JButton("Button 4"));
list.add(new JButton("Button 5"));
for (int i = 0; i < list.size(); i++) {
JButton button = (JButton) list.get(i);
button.setBackground(Color.white);
}
Bemærk den nødvendige eksplicit typekonvertering (også kaldet "cast") og den type usikkerhed, der er knyttet til den. Du kan utilsigtet ArrayListgemme et objekt i det , der ikke er en forekomst af klassen JButton. Oplysningerne om den nøjagtige type er, når de indsættes i listen tabt, så kompilatoren kan ikke forhindre ved kørsel, når eksplicit typekonvertering af JButtonen ClassCastExceptionforekommer.
Med generiske typer kan du gøre følgende i Java:
List<JButton> list = new ArrayList<JButton>();
list.add(new JButton("Button 1"));
list.add(new JButton("Button 2"));
list.add(new JButton("Button 3"));
list.add(new JButton("Button 4"));
list.add(new JButton("Button 5"));
for (int i = 0; i < list.size(); i++)
list.get(i).setBackground(Color.white);
En eksplicit typekonvertering er ikke længere nødvendig, når du læser ud, når du gemmer, er det kun muligt at gemme JButtons på ArrayList listen .
Fra Java7 er instantiering af generiske typer blevet forenklet. Den første linje i ovenstående eksempel kan skrives som følger siden Java 7:
List<JButton> list = new ArrayList<>();
Ved at kombinere generiske typer med udvidet til sløjfer kan ovenstående eksempel gøres mere kort:
List<JButton> list = new ArrayList<>();
list.add(new JButton("Button 1"));
list.add(new JButton("Button 2"));
list.add(new JButton("Button 3"));
list.add(new JButton("Button 4"));
list.add(new JButton("Button 5"));
for (JButton b: list)
b.setBackground(Color.white);
Følgende eksempelkode giver et eksempel på en generisk klasse, der indeholder to objekter af enhver type, men af samme type:
public class DoubleObject<T> {
private T object1;
private T object2;
public DoubleObject(T object1, T object2) {
this.object1 = object1;
this.object2 = object2;
}
public String toString() {
return this.object1 + ", " + this.object2;
}
public static void main(String[] args) {
DoubleObject<String> s = new DoubleObject<>("abc", "def");
DoubleObject<Integer> i = new DoubleObject<>(123, 456);
System.out.println("DoubleObject<String> s=" + s.toString());
System.out.println("DoubleObject<Integer> i=" + i.toString());
}
}
Tilfælde af varians
Følgende varianssager kan skelnes i Java. De tilbyder hver især fuldstændig uafhængig fleksibilitet i håndteringen af generiske typer og er hver især statisk typesikre.
Uvariation
I tilfælde af uforanderlighed er typeparameteren unik. På denne måde tilbyder invarians den størst mulige frihed, når man bruger typeparameteren. For eksempel er alle handlinger tilladt for de elementer i en ArrayList <Integer>, der også er tilladt, når man bruger et enkelt heltal direkte (inklusive autoboxing ). Eksempel:
List<Integer> list = new ArrayList<Integer>();
// ...
Integer x = list.get(index);
list.get(index).methodeVonInteger();
list.set(index, 98347); // Autoboxing, entspricht Integer.valueOf(98347)
int y = list.get(index); // Auto-Unboxing
Disse muligheder købes med ringe fleksibilitet i tildelingen af genstande fra selve den generiske klasse. F.eks. Er følgende ikke tilladt:
List<Number> list = new ArrayList<Integer>();
selvom heltal er afledt af tal. Årsagen er, at kompilatoren her ikke længere kan sikre, at der ikke opstår typefejl. Vi har haft dårlige erfaringer med arrays, der tillader en sådan opgave:
// OK, Integer[] ist abgeleitet von Number[]
Number[] array = new Integer[10];
// ArrayStoreException zur Laufzeit: Double -> Integer sind nicht
// zuweisungskompatibel
array[0] = new Double(5.0);
Kovarians
Arrays kaldes covariant , hvilket betyder:
eller mere generelt:
Arraytypen opfører sig på samme måde som typeparameteren med hensyn til arvshierarkiet. Kovarians er også mulig med generiske typer, men kun med begrænsninger, så typefejl kan udelukkes på kompileringstidspunktet.
Referencer har brug for med syntaksen ? udvider T skal markeres eksplicit som kovariant. T kaldes øvre typebound , som er den mest generelle typeparameter , der er tilladt.
List<? extends Number> list;
list = new ArrayList<Double>();
list = new ArrayList<Long>();
list = new ArrayList<Integer>();
// Typfehler vom Compiler
list.set(index, myInteger);
// OK aber Warnung vom Compiler: unchecked cast
((List<Integer>) list).set(index, myInteger);
Det er ikke muligt at gemme elementer på disse lister, fordi det som beskrevet ovenfor ikke er typesikkert (undtagelse: nul kan gemmes). En fejl opstår på kompileringstidspunktet. Mere generelt er opgaven
ikke tilladt.
Det er dog muligt at aflæse elementer:
Number n = list.get(index); // OK
Integer i = list.get(index); // Typfehler: Es muss sich bei '? extends Number'
// nicht um ein Integer handeln.
Integer j = (Integer) list.get(index); // OK
Opgaven
er derfor tilladt, men ikke opgaven
Generics, som arrays, tilbyder kovariant adfærd, men forbyder alle operationer, der er typesikre.
Modstrid
Contravariance beskriver opførelsen af arvshierarkiet af den generiske type mod hierarkiet for dens typeparameter. Anvendt på ovenstående eksempel vil dette betyde: En liste <Nummer> ville være tildelings-kompatibel til en liste <Dobbelt>. Dette gøres som følger:
List<? super Double> list;
list = new ArrayList<Number>();
list = new ArrayList<Double>();
list = new ArrayList<Object>();
Et objekt, der opfører sig kontroversielt, må ikke antage antagelser om, i hvilket omfang et element af type V er afledt af T, hvor T er det nederste typebind (i eksemplet med ? super Doubleer T Double). Derfor kan ovenstående lister ikke læses:
// Fehler: 'list' könnte vom Typ List<Object> sein
Number x = list.get(index);
// Fehler: 'list' könnte List<Object> oder List<Number> sein
Double x = list.get(index);
// Die einzige Ausnahme: Objects sind auf jeden Fall in der Liste
Object x = list.get(index);
Ikke tilladt, fordi det ikke er typesikkert, så er opgaven ? super T → (afledt af objekt)
Ikke svært at gætte: Til gengæld kan et element placeres på en sådan liste:
List<? super Number> list;
list.add(new Double(3.0)); // OK: 'list' hat immer den Typ List<Number>
// oder List<Basisklasse von Number>. Damit
// ist die Zuweisung Double -> T immer erlaubt.
Ubegrænset parametrisk polymorfisme
Sidst men ikke mindst tilbyder generiske lægemidler fuldstændig polymorf adfærd. Her kan der ikke gives noget udsagn om typeparametrene, fordi der ikke er angivet nogen grænse i begge retninger. Den wildcard blev defineret for denne. Det er repræsenteret af et spørgsmålstegn.
List<?> list;
list = new ArrayList<Integer>();
list = new ArrayList<Object>();
list = new ArrayList<String>();
// ...
Selve typeparameteren kan ikke bruges her, da der ikke er noget udsagn muligt. Kun opgaven T → Objekt er tilladt, da T bestemt er et objekt. Til gengæld er det garanteret, at koden kan fungere med alle Ts.
Dette kan være nyttigt, hvis du kun arbejder med den generiske type:
// Keine Informationen über den Typparameter nötig, kann ''beliebige'' Listen
// aufnehmen.
int readSize(List<?> list) {
return list.size();
}
For at gøre det klart, at jokertegn er unødvendige her, og at det faktisk ikke handler om nogen varians, gives følgende implementering af ovenstående funktion:
<T> int readSize(List<T> list) {
return list.size();
}
Weblinks
- Ina Brenner: Tutorial on Generics in Java af forfatteren af en bog om Java-certificering SCJP (tysk)
- Klaus Kreft, Angelika Langer: Artikelserie om generics i Java i Java- spektret (tysk)
- Kapitel om generiske datatyper fra Galileo Openbook Java er også en ø (tysk)