CodeGym /Java Blog /Willekeurig /Generieken op Java
John Squirrels
Niveau 41
San Francisco

Generieken op Java

Gepubliceerd in de groep Willekeurig
Hoi! We gaan het hebben over Java Generics. Ik moet zeggen dat je er veel van gaat leren! Niet alleen deze les, maar ook de volgende lessen staan ​​in het teken van generieke geneesmiddelen. Dus als je geïnteresseerd bent in generieke geneesmiddelen, vandaag heb je geluk: je leert veel over de kenmerken van generieke geneesmiddelen. En zo niet, geef jezelf dan op en ontspan! :) Dit is een heel belangrijk onderwerp en je moet het weten. Laten we beginnen met het simpele: het "wat" en het "waarom".

Wat zijn Java-generieken?

Generics zijn typen die een parameter hebben. Wanneer u een generiek type maakt, geeft u niet alleen een type op, maar ook het gegevenstype waarmee het zal werken. Ik vermoed dat het meest voor de hand liggende voorbeeld al in je opgekomen is: ArrayList! Dit is hoe we er meestal een in een programma maken:

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");
   }
}
Zoals je misschien wel kunt raden, is een kenmerk van deze lijst dat we er niet alles in kunnen proppen: het werkt uitsluitend met String- objecten. Laten we nu eens wat uitweiden over de geschiedenis van Java en proberen de vraag "waarom?" te beantwoorden. Om dit te doen, schrijven we onze eigen vereenvoudigde versie van de klasse ArrayList. Onze lijst weet alleen hoe gegevens moeten worden toegevoegd aan en opgehaald uit een interne 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;
   }
}
Stel dat we willen dat onze lijst alleen Integer s opslaat. We gebruiken geen generiek type. We willen geen expliciete "instanceof Integer "-controle opnemen in de add()- methode. Als we dat zouden doen, zou onze hele klasse alleen geschikt zijn voor Integer , en zouden we een vergelijkbare klasse moeten schrijven voor elk ander gegevenstype ter wereld! We vertrouwen op onze programmeurs en laten gewoon een opmerking achter in de code om ervoor te zorgen dat ze niets toevoegen dat we niet willen:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Een van de programmeurs heeft deze opmerking over het hoofd gezien en heeft per ongeluk meerdere strings in een lijst met getallen geplaatst en vervolgens hun som berekend:

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

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Wat is het ergste van deze situatie? Zeker niet de onzorgvuldigheid van de programmeur. Het ergste is dat onjuiste code op een belangrijke plaats in ons programma terecht is gekomen en met succes is gecompileerd. Nu zullen we de bug niet tegenkomen tijdens het schrijven van code, maar alleen tijdens het testen (en dit is het beste scenario!). Het oplossen van bugs in latere ontwikkelingsfasen kost veel meer, zowel in geld als in tijd. Dit is precies waar generieke software ons voordeel mee doet: een generieke klasse laat de ongelukkige programmeur de fout meteen ontdekken. Het programma wil gewoon niet compileren!

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!
   }
}
De programmeur realiseert zich onmiddellijk zijn of haar fout en wordt meteen beter. We hoefden trouwens niet onze eigen List- klasse te maken om dit soort fouten te zien. Verwijder gewoon de punthaken en typ ( <Integer> ) uit een gewone 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));
   }
}
Console-uitvoer:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
Met andere woorden, zelfs met de "native" mechanismen van Java kunnen we dit soort fouten maken en een onveilige collectie creëren. Als we deze code echter in een IDE plakken, krijgen we een waarschuwing: "Unchecked call to add(E) as a member of raw type of java.util.List" Er wordt ons verteld dat er iets mis kan gaan bij het toevoegen van een item naar een collectie die geen generiek type heeft. Maar wat betekent de uitdrukking "ruw type"? Een onbewerkt type is een generieke klasse waarvan het type is verwijderd. Met andere woorden, Lijst mijnLijst1 is een onbewerkt type . Het tegenovergestelde van een onbewerkt type is een generiek type — een generieke klasse met een indicatie van de geparametriseerde type(s) . Bijvoorbeeld Lijst<String> mijnLijst1. U vraagt ​​zich misschien af ​​waarom de taal het gebruik van onbewerkte typen toestaat ? De reden is simpel. De makers van Java hebben ondersteuning voor onbewerkte typen in de taal achtergelaten om compatibiliteitsproblemen te voorkomen. Tegen de tijd dat Java 5.0 werd uitgebracht (generics verschenen voor het eerst in deze versie), was er al veel code geschreven met onbewerkte typen . Als gevolg hiervan wordt dit mechanisme vandaag de dag nog steeds ondersteund. We hebben het klassieke boek "Effective Java" van Joshua Bloch herhaaldelijk genoemd in de lessen. Als een van de makers van de taal sloeg hij in zijn boek geen onbewerkte typen en generieke typen over.Wat zijn generieke geneesmiddelen in Java?  - 2Hoofdstuk 23 van het boek heeft een zeer welsprekende titel: "Gebruik geen onbewerkte typen in nieuwe code". Dit is wat u moet onthouden. Wanneer u generieke klassen gebruikt, mag u nooit een generiek type in een onbewerkt type veranderen .

Generieke methoden

Met Java kunt u individuele methoden parametriseren door zogenaamde generieke methoden te maken. Hoe zijn dergelijke methoden nuttig? Bovenal zijn ze nuttig omdat ze u laten werken met verschillende soorten methodeparameters. Als dezelfde logica veilig kan worden toegepast op verschillende typen, kan een generieke methode een goede oplossing zijn. Beschouw dit als een heel eenvoudig voorbeeld: stel dat we een lijst hebben met de naam myList1 . We willen alle waarden uit de lijst verwijderen en alle lege ruimtes vullen met nieuwe waarden. Zo ziet onze klas eruit met een generieke methode:

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);
   }
}
Let op de syntaxis. Het ziet er een beetje ongewoon uit:

public static <T> void fill(List<T> list, T val)
We schrijven <T> voor het retourtype. Dit geeft aan dat we te maken hebben met een generieke methode. In dit geval accepteert de methode 2 parameters als invoer: een lijst met T-objecten en nog een apart T-object. Door <T> te gebruiken, parametriseren we de parametertypes van de methode: we kunnen geen lijst met Strings en een Integer doorgeven. Een lijst met Strings en een String, een lijst met Integers en een Integer, een lijst met onze eigen Cat- objecten en een ander Cat- object - dat is wat we moeten doen. De methode main() illustreert hoe de methode fill() eenvoudig kan worden gebruikt om met verschillende soorten gegevens te werken. Eerst gebruiken we de methode met een lijst Strings en een String als invoer, en vervolgens met een lijst Integers en een Integer. Console-uitvoer:

[New String, New String, New String] [888, 888, 888]
Stel je voor dat we geen generieke methoden hadden en de logica van de methode fill() nodig hadden voor 30 verschillende klassen. We zouden dezelfde methode 30 keer moeten schrijven voor verschillende gegevenstypen! Maar dankzij generieke methoden kunnen we onze code hergebruiken! :)

Generieke lessen

U bent niet beperkt tot de generieke klassen die in de standaard Java-bibliotheken worden aangeboden - u kunt uw eigen klassen maken! Hier is een eenvoudig voorbeeld:

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!
   }
}
Onze Box<T> -klasse is een generieke klasse. Zodra we tijdens het maken een gegevenstype ( <T> ) hebben toegewezen, kunnen we er geen objecten van andere typen meer in plaatsen. Dit is te zien in het voorbeeld. Bij het maken van ons object hebben we aangegeven dat het zou werken met Strings:

Box<String> stringBox = new Box<>();
En in de laatste regel code, wanneer we het nummer 12345 in de doos proberen te plaatsen, krijgen we een compilatiefout! Het is zo makkelijk! We hebben onze eigen generieke klasse gemaakt! :) Daarmee komt de les van vandaag ten einde. Maar we nemen geen afscheid van generieke geneesmiddelen! In de volgende lessen zullen we het hebben over meer geavanceerde functies, dus ga niet weg! ) Om te versterken wat je hebt geleerd, raden we je aan een videoles van onze Java-cursus te bekijken
Veel succes met je studie! :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION