Generell programmering i Java

Generell programmering i Java harblitt muliggjortav såkalte generics siden Java 1.5. Begrepet er synonymt med "parametrerte typer". Tanken bak dette er å introdusere flere variabler for typer. Disse typevariablene representerer ukjente typer på implementeringstidspunktet. Disse typevariablene erstattes bare av konkrete typer når klassene, grensesnittene og metodene brukes. På denne måten kan typesikker programmering vanligvis garanteres. Men ikke alltid.

Konseptet

Fra versjon 5.0 ("Tiger", publisert i 2004), gir Java- programmeringsspråket også generiske, et syntaktisk middel for generell programmering . Dette gjør at klasser og metoder (metoder også uavhengig av klassene sine) kan parametreres med typer . Dette åpner for noen lignende muligheter for språket som kan sammenlignes med malene i C ++ .

I prinsippet er det imidlertid betydelige forskjeller. Mens den type som parameteren er parametrisert i Java via grensesnittet , i C ++ den typen para i seg selv er parameterisert direkte . Kildekoden til en C ++ -mal må være tilgjengelig for brukeren (dvs. når typeparameteren er satt inn), mens en generisk Java-type også kan publiseres som en oversatt bykode. Kompilatoren produserer duplisert målkode for forskjellige spesifikt brukte typeparametere.

For eksempel gir funksjonen std :: sort i C ++ muligheten til å sortere alle containere som tilbyr bestemte metoder (her begynner spesielt () og end (), som hver returnerer en iterator) og hvis typeparametere implementerer 'operator <' (eller en annen sammenligningsfunksjon ble eksplisitt spesifisert). En ulempe med dette systemet er at det er vanskeligere (for programmereren!) Å oversette. Kompilatoren har ikke noe annet alternativ enn å erstatte typeparameteren i hvert tilfelle med ønsket spesifikk type og å kompilere hele koden på nytt.

Når det gjelder upassende typeparametere og andre problemer, kan det veldig lett oppstå kompliserte og uforståelige kompilatormeldinger, som ganske enkelt er relatert til det faktum at de spesifikke kravene til typeparametrene er ukjente. Arbeid med C ++ maler krever derfor fullstendig dokumentasjon av kravene til en typeparameter. Med malmetaprogrammering kan de fleste krav (basisklasse, tilgjengelighet av metoder, kopierbarhet, tildelbarhet osv.) Også spørres i spesielle konstruksjoner, noe som resulterer i mer lesbare feilmeldinger. Selv om de er i samsvar med standardene, støttes ikke disse konstruksjonene av alle kompilatorer .

I kontrast er de generiske klassene og metodene i Java klar over begrensningene for deres egne typeparametere. For å sortere en samling (uten en komparator), må elementene den inneholder være av sammenlignbar type, dvs. har implementert dette grensesnittet. Kompilatoren må bare sjekke om typeparameteren er en undertype av Comparable og kan dermed sikre at koden er riktig (dvs. den nødvendige metoden comparTo er tilgjengelig). Videre brukes en og samme kode for alle spesifikke typer og dupliseres ikke hver gang.

Nyttige eksempler

Et program bruker ett til å lagre ArrayListen liste over JButtons.

Så langt var ArrayList løst 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);
}

Legg merke til den nødvendige eksplisitte typekonvertering (også kalt "cast") og typen usikkerhet knyttet til den. Du kan utilsiktet ArrayListlagre et objekt i det som ikke er en forekomst av klassen JButton. Informasjonen om den eksakte typen er når den settes inn i listen tapt, slik at kompilatoren ikke kan forhindre på kjøretid når eksplisitt type konvertering av JButtonen ClassCastExceptionforekommer.

Med generiske typer kan du gjø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 eksplisitt typekonvertering er ikke lenger nødvendig når du leser opp, når du lagrer er det bare mulig å lagre JButtons i ArrayList listen .

Fra og med Java7 er instantiering av generiske typer forenklet. Den første linjen i eksemplet ovenfor kan skrives som følger siden Java 7:

List<JButton> list = new ArrayList<>();

Ved å kombinere generiske typer med utvidet for løkker, kan eksemplet ovenfor gjøres 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 gir et eksempel på en generisk klasse som inneholder to objekter av hvilken som helst type, men av 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());
    }
}

Tilfeller av varians

Følgende tilfeller av varians kan skilles ut i Java. De tilbyr hver sin helt uavhengige fleksibilitet når det gjelder generiske typer, og er hver for seg helt statisk typesikre.

Invarians

I tilfelle invarians er typeparameteren unik. På denne måten gir invarians størst mulig frihet når du bruker typeparameteren. For eksempel er alle handlinger tillatt for elementene i en ArrayList <Integer> som også er tillatt når du bruker et enkelt heltall direkte (inkludert autoboksing ). 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 mulighetene er kjøpt med liten fleksibilitet ved tildeling av gjenstander fra selve generiklassen. For eksempel er følgende ikke tillatt:

List<Number> list = new ArrayList<Integer>();

selv om Integer er avledet fra Number. Årsaken er at her kan ikke kompilatoren lenger sikre at det ikke oppstår typefeil. Vi har hatt dårlige erfaringer med matriser som tillater en slik oppgave:

// 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 kalles covariant , som betyr:

Fra T strekker V seg etter: T [] strekker seg V []

eller mer generelt:

Fra T strekker V seg etter: GenericType <T> utvider GenericType <V>

Array-typen oppfører seg på samme måte som typeparameteren med hensyn til arvshierarkiet. Kovarians er også mulig med generiske typer, men bare med begrensninger slik at typefeil kan ekskluderes på kompileringstidspunktet.

Referanser trenger med syntaksen ? utvider T må være eksplisitt merket som kovariant. T kalles øvre typebound , som er den mest generelle typeparameteren som er tillatt.

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 mulig å lagre elementer i disse listene fordi, som beskrevet ovenfor, er dette ikke typesikkert (unntak: null kan lagres). Det oppstår en feil på kompileringstidspunktet. Mer generelt er oppgaven

? ? utvider T

ikke tillatt.

Det er imidlertid mulig å lese opp 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

Oppgaven

? utvider TT (eller baseklasse)

er derfor tillatt, men ikke oppgaven

? strekker seg Tavledet fra T

Generiske, som matriser, tilbyr kovariant oppførsel, men forbyr alle operasjoner som er usikre.

Kontravaranse

Contravariance beskriver oppførselen til arvshierarkiet av den generiske typen i strid med hierarkiet til dens typeparameter. Påført eksemplet ovenfor vil dette bety: En liste <Nummer> vil være tildelingskompatibel til en liste <Dobbelt>. Dette gjøres som følger:

List<? super Double> list;
list = new ArrayList<Number>();
list = new ArrayList<Double>();
list = new ArrayList<Object>();

Et objekt som oppfører seg på en motstridende måte, må ikke gjøre noen forutsetninger om i hvilken grad et element av type V er avledet fra T, hvor T er det nedre typebundet (i eksemplet med ? super Doubleer T Double). Derfor kan ikke listene ovenfor leses:

// 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 tillatt, for ikke typesikkert, så er oppdraget ? super T → (avledet fra Objekt)

Ikke vanskelig å gjette: Til gjengjeld kan et element plasseres i en slik 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.

Ubegrenset parametrisk polymorfisme

Sist men ikke minst, generiske legemidler tilbyr fullstendig polymorf oppførsel. Ingen uttalelser om typeparametrene kan gjøres her, fordi ingen grenser er spesifisert i begge retninger. Den wildcard ble definert for dette. Det er representert med et spørsmålstegn.

List<?> list;
list = new ArrayList<Integer>();
list = new ArrayList<Object>();
list = new ArrayList<String>();
// ...

Selve typeparameteren kan ikke brukes her, ettersom ingen uttalelser er mulige. Bare oppgaven T → Objekt er tillatt, siden T definitivt er et objekt. Til gjengjeld er det garantert at koden kan fungere med alle Ts.

Noe som dette kan være nyttig hvis du bare jobber med generisk type:

// Keine Informationen über den Typparameter nötig, kann ''beliebige'' Listen
// aufnehmen.
int readSize(List<?> list) {
    return list.size();
}

For å gjøre det klart at jokertegn er unødvendige her, og at det faktisk ikke handler om noen avvik, er følgende implementering av ovennevnte funksjon gitt:

<T> int readSize(List<T> list) {
    return list.size();
}

weblenker

Individuelle bevis

  1. Java og Scalas type systemer er ikke lyd .