CodeGym /Java-blogg /Tilfeldig /Generikk i Java
John Squirrels
Nivå
San Francisco

Generikk i Java

Publisert i gruppen
Hei! Vi skal snakke om Java Generics. Jeg må si at du kommer til å lære mye! Ikke bare denne leksjonen, men også de neste leksjonene, vil bli viet til generiske legemidler. Så hvis du er interessert i generiske legemidler, er dagens heldige dag: du vil lære mye om funksjonene til generiske legemidler. Og hvis ikke, si opp selv og slapp av! :) Dette er et veldig viktig tema, og du må vite det. La oss starte med det enkle: "hva" og "hvorfor".

Hva er Java Generics?

Generiske er typer som har en parameter. Når du oppretter en generisk type, spesifiserer du ikke bare en type, men også datatypen den skal fungere med. Jeg antar at det mest åpenbare eksemplet allerede har dukket opp: ArrayList! Slik lager vi vanligvis en i et program:

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Som du kanskje gjetter, er en funksjon i denne listen at vi ikke kan stappe alt inn i den: den fungerer utelukkende med String- objekter. La oss nå ta en liten digresjon inn i Javas historie og prøve å svare på spørsmålet "hvorfor?" For å gjøre dette, skriver vi vår egen forenklede versjon av ArrayList-klassen. Listen vår vet bare hvordan du legger til data til og henter data fra en intern matrise:

public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Anta at vi vil at listen vår bare skal lagre heltall . Vi bruker ikke en generisk type. Vi ønsker ikke å inkludere en eksplisitt "instanceof Integer "-sjekk i add()- metoden. Hvis vi gjorde det, ville hele klassen vår kun passet for Integer , og vi måtte skrive en lignende klasse for alle andre datatyper i verden! Vi vil stole på våre programmerere, og bare legge igjen en kommentar i koden for å sikre at de ikke legger til noe vi ikke vil ha:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
En av programmererne gikk glipp av denne kommentaren og la ved et uhell flere strenger i en liste med tall og beregnet deretter summen deres:

public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Konsoll utgang:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Hva er det verste med denne situasjonen? Absolutt ikke uforsiktigheten til programmereren. Det verste er at feil kode havnet på en viktig plass i programmet vårt og kompilert vellykket. Nå vil vi støte på feilen ikke mens vi skriver kode, men bare under testing (og dette er det beste scenarioet!). Å fikse feil i senere stadier av utviklingen koster mye mer – både i form av penger og tid. Det er nettopp her generika er til nytte for oss: en generisk klasse lar den uheldige programmereren oppdage feilen med en gang. Programmet vil rett og slett ikke kompilere!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();
      
       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Programmereren innser umiddelbart feilen sin og blir umiddelbart bedre. Forresten, vi trengte ikke å lage vår egen listeklasse for å se denne typen feil. Bare fjern vinkelparentesene og skriv ( <Integer> ) fra en vanlig ArrayList!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Konsoll utgang:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
Med andre ord, selv ved å bruke Javas "native" mekanismer, kan vi gjøre denne typen feil og lage en usikker samling. Imidlertid, hvis vi limer inn denne koden i en IDE, får vi en advarsel: "Uavmerket kall for å legge til(E) som medlem av råtypen java.util.List" Vi blir fortalt at noe kan gå galt når du legger til et element til en samling som mangler en generisk type. Men hva betyr uttrykket "rå type"? En råtype er en generisk klasse hvis type er fjernet. Med andre ord, List myList1 er en råtype . Det motsatte av en råtype er en generisk type - en generisk klasse med en indikasjon på den eller de parameteriserte typene . For eksempel List<String> myList1. Du kan spørre hvorfor språket tillater bruk av råtyper ? Grunnen er enkel. Javas skapere forlot støtte for råtyper i språket for å unngå å skape kompatibilitetsproblemer. Da Java 5.0 ble utgitt (generikk først dukket opp i denne versjonen), var mye kode allerede blitt skrevet med råtyper . Som et resultat er denne mekanismen fortsatt støttet i dag. Vi har gjentatte ganger nevnt Joshua Blochs klassiske bok «Effektiv Java» i timene. Som en av skaperne av språket hoppet han ikke over råtyper og generiske typer i boken sin. Hva er generiske medisiner i Java?  - 2Kapittel 23 i boken har en svært veltalende tittel: «Ikke bruk råtyper i ny kode» Dette er hva du må huske. Når du bruker generiske klasser, må du aldri gjøre en generisk type til en råtype .

Generiske metoder

Java lar deg parameterisere individuelle metoder ved å lage såkalte generiske metoder. Hvordan er slike metoder nyttige? Fremfor alt er de nyttige ved at de lar deg jobbe med ulike typer metodeparametere. Hvis den samme logikken trygt kan brukes på forskjellige typer, kan en generisk metode være en flott løsning. Tenk på dette som et veldig enkelt eksempel: Anta at vi har en liste som heter myList1 . Vi ønsker å fjerne alle verdier fra listen og fylle alle de tomme plassene med nye verdier. Slik ser klassen vår ut med en generisk metode:

public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Vær oppmerksom på syntaksen. Det ser litt uvanlig ut:

public static <T> void fill(List<T> list, T val)
Vi skriver <T> før returtypen. Dette indikerer at vi har å gjøre med en generisk metode. I dette tilfellet aksepterer metoden 2 parametere som inngang: en liste over T-objekter og et annet separat T-objekt. Ved å bruke <T> parametriserer vi metodens parametertyper: vi kan ikke sende inn en liste med strenger og et heltall. En liste over strenger og en streng, en liste over heltall og et heltall, en liste over våre egne Cat- objekter og et annet Cat- objekt – det er det vi må gjøre. Main ()- metoden illustrerer hvordan fill()- metoden enkelt kan brukes til å arbeide med ulike typer data. Først bruker vi metoden med en liste over strenger og en streng som input, og deretter med en liste over heltall og et heltall. Konsoll utgang:

[New String, New String, New String] [888, 888, 888]
Tenk om vi ikke hadde generiske metoder og trengte logikken til fill()- metoden for 30 forskjellige klasser. Vi må skrive samme metode 30 ganger for forskjellige datatyper! Men takket være generiske metoder kan vi gjenbruke koden vår! :)

Generiske klasser

Du er ikke begrenset til de generiske klassene som tilbys i standard Java-biblioteker – du kan lage dine egne! Her er et enkelt eksempel:

public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
Vår Box<T> -klasse er en generisk klasse. Når vi tilordner en datatype ( <T> ) under opprettelsen, er vi ikke lenger i stand til å plassere objekter av andre typer i den. Dette kan sees i eksempelet. Da vi opprettet objektet vårt, indikerte vi at det ville fungere med Strings:

Box<String> stringBox = new Box<>();
Og i den siste kodelinjen, når vi prøver å sette nummeret 12345 i boksen, får vi en kompileringsfeil! Så enkelt er det! Vi har laget vår egen generiske klasse! :) Med det går dagens leksjon mot slutten. Men vi sier ikke farvel til generika! I de neste leksjonene skal vi snakke om mer avanserte funksjoner, så ikke gå bort! ) For å forsterke det du lærte, foreslår vi at du ser en videoleksjon fra Java-kurset vårt
Lykke til med studiene! :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION