CodeGym /Java blog /Tilfældig /Generisk i Java
John Squirrels
Niveau
San Francisco

Generisk i Java

Udgivet i gruppen
Hej! Vi skal tale om Java Generics. Jeg må sige, at du vil lære meget! Ikke kun denne lektion, men også de næste par lektioner vil blive afsat til generiske lægemidler. Så hvis du er interesseret i generiske lægemidler, er du heldig dag i dag: du vil lære meget om funktionerne ved generiske lægemidler. Og hvis ikke, resign dig selv og slap af! :) Dette er et meget vigtigt emne, og du skal vide det. Lad os starte med det enkle: "hvad" og "hvorfor".

Hvad er Java Generics?

Generiske er typer, der har en parameter. Når du opretter en generisk type, angiver du ikke kun en type, men også den datatype, den vil arbejde med. Jeg gætter på, at det mest åbenlyse eksempel allerede er dukket op: ArrayList! Sådan opretter vi normalt 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 måske gætter, er en funktion ved denne liste, at vi ikke kan fylde alt i den: den fungerer udelukkende med String- objekter. Lad os nu tage en lille digression ind i Javas historie og prøve at besvare spørgsmålet "hvorfor?" For at gøre dette vil vi skrive vores egen forenklede version af ArrayList-klassen. Vores liste ved kun, hvordan man tilføjer data til og henter data fra et internt array:

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;
   }
}
Antag, at vi ønsker, at vores liste kun skal gemme heltal . Vi bruger ikke en generisk type. Vi ønsker ikke at inkludere et eksplicit "instanceof Integer "-tjek i add()- metoden. Hvis vi gjorde det, ville hele vores klasse kun være egnet til Integer , og vi ville være nødt til at skrive en lignende klasse for hver anden datatype i verden! Vi vil stole på vores programmører og lægge en kommentar i koden for at sikre, at de ikke tilføjer noget, vi ikke ønsker:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
En af programmørerne gik glip af denne kommentar og satte uforvarende flere strenge i en liste med tal og beregnede derefter deres sum:

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);
   }
}
Konsoludgang:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Hvad er den værste del af denne situation? Bestemt ikke programmørens skødesløshed. Det værste er, at forkert kode endte et vigtigt sted i vores program og kompileret med succes. Nu vil vi støde på fejlen, ikke mens vi skriver kode, men kun under test (og dette er det bedste scenario!). At rette fejl i senere udviklingsstadier koster meget mere - både i form af penge og tid. Det er netop her, generika gavner os: En generisk klasse lader den uheldige programmør opdage fejlen med det samme. Programmet vil simpelthen 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!
   }
}
Programmøren indser straks sin fejl og bliver øjeblikkeligt bedre. I øvrigt behøvede vi ikke at oprette vores egen listeklasse for at se denne slags fejl. Du skal blot fjerne vinkelparenteserne og skrive ( <Integer> ) fra en almindelig 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));
   }
}
Konsoludgang:

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 at bruge Javas "native" mekanismer, kan vi begå denne form for fejl og skabe en usikker samling. Men hvis vi indsætter denne kode i en IDE, får vi en advarsel: "Ukontrolleret kald til tilføje(E) som medlem af rå type java.util.List" Vi får at vide, at noget kan gå galt, når du tilføjer et element til en samling, der mangler en generisk type. Men hvad betyder udtrykket "rå type"? En rå type er en generisk klasse, hvis type er blevet fjernet. Med andre ord er List myList1 en rå type . Det modsatte af en rå type er en generisk type — en generisk klasse med en angivelse af den eller de parametriserede type(r) . For eksempel, List<String> myList1. Du spørger måske, hvorfor sproget tillader brugen af ​​råtyper ? Årsagen er enkel. Javas skabere efterlod understøttelse af råtyper i sproget for at undgå at skabe kompatibilitetsproblemer. På det tidspunkt, hvor Java 5.0 blev udgivet (generics dukkede først op i denne version), var en masse kode allerede blevet skrevet ved hjælp af råtyper . Som følge heraf understøttes denne mekanisme stadig i dag. Vi har gentagne gange nævnt Joshua Blochs klassiske bog "Effektiv Java" i lektionerne. Som en af ​​skaberne af sproget sprang han ikke råtyper og generiske typer over i sin bog. Hvad er generiske lægemidler i Java?  - 2Kapitel 23 i bogen har en meget veltalende titel: "Brug ikke råtyper i ny kode" Dette er hvad du skal huske. Når du bruger generiske klasser, skal du aldrig forvandle en generisk type til en rå type .

Generiske metoder

Java lader dig parametrisere individuelle metoder ved at skabe såkaldte generiske metoder. Hvordan er sådanne metoder nyttige? Frem for alt er de nyttige ved, at de lader dig arbejde med forskellige typer metodeparametre. Hvis den samme logik sikkert kan anvendes på forskellige typer, kan en generisk metode være en god løsning. Betragt dette som et meget simpelt eksempel: Antag, at vi har en eller anden liste kaldet myList1 . Vi ønsker at fjerne alle værdier fra listen og udfylde alle de tomme pladser med nye værdier. Sådan ser vores klasse ud 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 opmærksom på syntaksen. Det ser lidt usædvanligt ud:

public static <T> void fill(List<T> list, T val)
Vi skriver <T> før returtypen. Dette indikerer, at vi har at gøre med en generisk metode. I dette tilfælde accepterer metoden 2 parametre som input: en liste over T-objekter og et andet separat T-objekt. Ved at bruge <T> parametrerer vi metodens parametertyper: vi kan ikke sende en liste over strenge og et heltal ind. En liste over strenge og en streng, en liste over heltal og et heltal, en liste over vores egne Cat- objekter og et andet Cat- objekt – det er det, vi skal gøre. Main ()- metoden illustrerer, hvordan fill()- metoden nemt kan bruges til at arbejde med forskellige typer data. Først bruger vi metoden med en liste over strenge og en streng som input, og derefter med en liste over heltal og et heltal. Konsoludgang:

[New String, New String, New String] [888, 888, 888]
Forestil dig, hvis vi ikke havde generiske metoder og havde brug for logikken i fill() -metoden for 30 forskellige klasser. Vi skulle skrive den samme metode 30 gange for forskellige datatyper! Men takket være generiske metoder kan vi genbruge vores kode! :)

Generiske klasser

Du er ikke begrænset til de generiske klasser i standard Java-biblioteker - du kan oprette dine egne! Her er et simpelt 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!
   }
}
Vores Box<T> -klasse er en generisk klasse. Når vi først tildeler en datatype ( <T> ) under oprettelsen, er vi ikke længere i stand til at placere objekter af andre typer i den. Dette kan ses i eksemplet. Da vi oprettede vores objekt, angav vi, at det ville fungere med Strings:

Box<String> stringBox = new Box<>();
Og i den sidste kodelinje, når vi forsøger at sætte nummeret 12345 inde i boksen, får vi en kompileringsfejl! Så nemt er det! Vi har skabt vores egen generiske klasse! :) Dermed slutter dagens lektion. Men vi siger ikke farvel til generika! I de næste lektioner vil vi tale om mere avancerede funktioner, så lad være med at gå væk! ) For at styrke det, du har lært, foreslår vi, at du ser en videolektion fra vores Java-kursus
Held og lykke med dine studier! :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION