CodeGym /Java Blog /Willekeurig /Jokertekens in generieke geneesmiddelen
John Squirrels
Niveau 41
San Francisco

Jokertekens in generieke geneesmiddelen

Gepubliceerd in de groep Willekeurig
Hoi! Laten we doorgaan met onze studie van generieke geneesmiddelen. U hebt er al veel kennis over opgedaan in eerdere lessen (over het gebruik van varargs bij het werken met generieke geneesmiddelen en over het wissen van typen ), maar er is een belangrijk onderwerp dat we nog niet hebben overwogen: wildcards . Dit is een zeer belangrijk kenmerk van generieke geneesmiddelen. Zo erg zelfs dat we er een aparte les aan hebben gewijd! Dat gezegd hebbende, er is niets ingewikkelds aan wildcards. Dat zie je meteen :) Jokertekens in generieke geneesmiddelen - 1Laten we eens kijken naar een voorbeeld:

public class Main {

   public static void main(String[] args) {
      
       String str = new String("Test!");
       // No problem
       Object obj = str;
      
       List<String> strings = new ArrayList<String>();
       // Compilation error!
       List<Object> objects = strings;
   }
}
Wat is hier aan de hand? We zien twee zeer vergelijkbare situaties. In het geval casten we een Stringobject naar een Objectobject. Er zijn hier geen problemen - alles werkt zoals verwacht. Maar in de tweede situatie genereert de compiler een fout. Maar we doen hetzelfde, nietwaar? Deze keer gebruiken we gewoon een verzameling van verschillende objecten. Maar waarom treedt de fout op? Wat is het verschil? Gieten we één Stringobject naar een Objectof 20 objecten? Er is een belangrijk onderscheid tussen een object en een verzameling objecten . Als de Bklas een kind is van de Aklas, dan Collection<B>is het geen kind van Collection<A>. Daarom konden we onze cast niet List<String>naar aList<Object>. Stringis een kind van Object, maar List<String>is geen kind van List<Object>. Dit lijkt misschien niet super intuïtief. Waarom hebben de makers van de taal het zo gemaakt? Laten we ons voorstellen dat de compiler ons geen fout geeft:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
In dit geval zouden we bijvoorbeeld het volgende kunnen doen:

objects.add(new Object());
String s = strings.get(0);
Omdat de compiler ons geen enkele fout gaf en ons in staat stelde een List<Object>verwijzing te maken die verwijst naar strings, kunnen we elk oud Objectobject aan de stringsverzameling toevoegen! We hebben dus de garantie verloren dat onze verzameling alleen de Stringobjecten bevat die worden gespecificeerd door het argument type in de generieke type-aanroeping . Met andere woorden, we zijn het belangrijkste voordeel van generieke geneesmiddelen kwijtgeraakt: typeveiligheid. En omdat de compiler ons er niet van weerhield om dit te doen, krijgen we alleen een foutmelding tijdens runtime, wat altijd veel erger is dan een compilatiefout. Om dit soort situaties te voorkomen, geeft de compiler ons een foutmelding:

// Compilation error
List<Object> objects = strings;
...en herinnert ons eraan dat het List<String>geen afstammeling is van List<Object>. Dit is een ijzersterke regel voor generieke geneesmiddelen en het moet onthouden worden wanneer u ermee werkt. Laten we verder gaan. Stel dat we een kleine klassenhiërarchie hebben:

public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
De hiërarchie wordt bekroond door een eenvoudige klasse Animal, die wordt geërfd door Pet. Huisdier heeft 2 subklassen: Hond en Kat. Stel nu dat we een eenvoudige methode moeten maken iterateAnimals(). De methode zou een verzameling van alle dieren ( Animal, Pet, Cat, Dog) moeten nemen, alle elementen moeten herhalen en tijdens elke iteratie een bericht op de console moeten weergeven. Laten we proberen zo'n methode te schrijven:

public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Het lijkt erop dat het probleem is opgelost! Echter, zoals we onlangs vernamen, List<Cat>, List<Dog>en List<Pet>zijn geen afstammelingen van List<Animal>! Dit betekent dat wanneer we de methode proberen aan te roepen iterateAnimals()met een lijst met katten, we een compilatiefout krijgen:

import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Another iteration in the loop!");
       }
   }

   public static void main(String[] args) {


       List<Cat> cats = new ArrayList<>();
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());

       // Compilation error!
       iterateAnimals(cats);
   }
}
De situatie ziet er niet goed uit voor ons! Moeten we aparte methoden schrijven voor het opsommen van elk soort dier? Eigenlijk niet, dat doen we niet :) En toevallig helpen wildcards ons hierbij! We kunnen het probleem met één eenvoudige methode oplossen met behulp van de volgende constructie:

public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Dit is een wildcard. Om precies te zijn, dit is de eerste van verschillende soorten wildcards. Het staat bekend als wildcards met een bovengrens en wordt uitgedrukt door ? strekt zich uit . Wat vertelt deze constructie ons? Dit betekent dat de methode een verzameling Animalobjecten accepteert of een verzameling objecten van welke klasse dan ook die afstamt van Animal(? Breidt Animal uit). Met andere woorden, de methode kan een verzameling Animal, Pet, Dog, of Catobjecten accepteren - het maakt geen verschil. Laten we onszelf ervan overtuigen dat het werkt:

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
Console-uitvoer:

Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
We hebben in totaal 4 collecties en 8 objecten gemaakt en er zijn precies 8 ingangen op de console. Alles werkt geweldig! :) Met het jokerteken konden we eenvoudig de noodzakelijke logica die aan specifieke typen is gekoppeld, in één enkele methode inpassen. We hebben de noodzaak geëlimineerd om voor elk type dier een aparte methode te schrijven. Stel je voor hoeveel methoden we nodig zouden hebben als onze applicatie door een dierentuin of een veterinair kantoor zou worden gebruikt :) Maar laten we nu eens naar een andere situatie kijken. Onze overervingshiërarchie blijft ongewijzigd: de klasse op het hoogste niveau is Animal, met de Petklasse er net onder, en de klassen Catand Dogop het volgende niveau. Nu moet je de methode herschrijven iterateAnimals()zodat je met elk type dier kunt werken, behalve met honden . Dat wil zeggen, het zou moeten accepteren Collection<Animal>,Collection<Pet>of Collection<Car>, maar het zou niet moeten werken met Collection<Dog>. Hoe kunnen we dit bereiken? Het lijkt erop dat we weer voor het vooruitzicht staan ​​om voor elk type een aparte methode te schrijven :/ Hoe leggen we anders aan de compiler uit wat we willen dat er gebeurt? Het is eigenlijk heel simpel! Opnieuw komen wildcards ons hier te hulp. Maar deze keer gebruiken we een ander type wildcard: een wildcard met een lagere begrenzing , die wordt uitgedrukt met super .

public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Another iteration in the loop!");
   }
}
Hier is het principe vergelijkbaar. De <? super Cat>constructie vertelt de compiler dat de methode een verzameling objecten of een voorouder van de klasse als invoer iterateAnimals()kan accepteren . In dit geval komen de klasse, zijn ouder , en de ouder van zijn ouder, allemaal overeen met deze beschrijving. De klasse komt niet overeen met onze beperking, dus een poging om de methode met een argument te gebruiken zal resulteren in een compilatiefout: CatCatCatPetAnimalDogList<Dog>

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
  
   // Compilation error!
   iterateAnimals(dogs);
}
We hebben ons probleem opgelost, en wederom bleken wildcards erg handig :) Hiermee is de les ten einde. Nu zie je hoe belangrijk generieke geneesmiddelen zijn in je studie van Java - we hebben er 4 hele lessen over gehad! Maar nu ben je goed thuis in het onderwerp en kun je je vaardigheden bewijzen in sollicitatiegesprekken :) En nu is het tijd om terug te gaan naar de taken! Veel succes met je studie! :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION