CodeGym/Java blogg/Slumpmässig/Jokertecken i generika
John Squirrels
Nivå
San Francisco

Jokertecken i generika

Publicerad i gruppen
Hej! Låt oss fortsätta vår studie av generika. Du har redan fått en stor mängd kunskap om dem från tidigare lektioner (om att använda varargs när du arbetar med generika och om typradering ), men det finns ett viktigt ämne som vi ännu inte har övervägt — jokertecken . Detta är en mycket viktig egenskap hos generika. Så mycket att vi har ägnat en separat lektion åt det! Som sagt, det är inget särskilt komplicerat med jokertecken. Du kommer att se det direkt :) Jokertecken i generika - 1Låt oss titta på ett exempel:
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;
   }
}
Vad händer här? Vi ser två mycket liknande situationer. I fallet gjuter vi ett Stringobjekt till ett Objectobjekt. Det är inga problem här - allt fungerar som förväntat. Men i den andra situationen genererar kompilatorn ett fel. Men vi gör väl samma sak? Den här gången använder vi helt enkelt en samling av flera föremål. Men varför uppstår felet? Vad är skillnaden? Gjuter vi ett Stringobjekt till ett Objecteller 20 objekt? Det finns en viktig skillnad mellan ett objekt och en samling objekt . Om Bklassen är ett barn i Aklassen, Collection<B>är det inte ett barn till Collection<A>. Det är därför vi inte kunde kasta vår List<String>till enList<Object>. Stringär ett barn till Object, men List<String>är inte ett barn till List<Object>. Detta kanske inte verkar superintuitivt. Varför gjorde språkets skapare det så här? Låt oss föreställa oss att kompilatorn inte ger oss ett fel:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
I det här fallet kan vi till exempel göra följande:
objects.add(new Object());
String s = strings.get(0);
Eftersom kompilatorn inte gav oss något fel och tillät oss att skapa en List<Object>referens som pekar på strings, kan vi lägga till vilket gammalt Objectobjekt som helst till stringssamlingen! Därmed har vi förlorat garantin att vår samling endast innehåller de Stringobjekt som specificeras av typargumentet i den generiska typanropet . Med andra ord har vi förlorat den största fördelen med generika – typsäkerhet. Och eftersom kompilatorn inte hindrade oss från att göra detta, kommer vi att få ett fel endast vid körning, vilket alltid är mycket värre än ett kompileringsfel. För att förhindra situationer som denna ger kompilatorn oss ett fel:
// Compilation error
List<Object> objects = strings;
...och påminner oss om att det List<String>inte är en ättling till List<Object>. Detta är en järnklädd regel för generika, och den måste komma ihåg när du arbetar med dem. Låt oss gå vidare. Anta att vi har en liten klasshierarki:
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()");
   }
}
Hierarkin toppas av en enkel djurklass, som ärvs av Pet. Husdjur har 2 underklasser: Hund och Katt. Anta nu att vi måste skapa en enkel iterateAnimals()metod. Metoden bör ta en samling av alla djur ( Animal, Pet, Cat, Dog), iterera över alla element och visa ett meddelande på konsolen under varje iteration. Låt oss försöka skriva en sådan metod:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Det verkar som att problemet är löst! Men som vi nyligen lärde oss, List<Cat>och List<Dog>är List<Pet>inte ättlingar till List<Animal>! Det betyder att när vi försöker anropa iterateAnimals()metoden med en lista över katter får vi ett kompileringsfel:
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);
   }
}
Läget ser inte bra ut för oss! Måste vi skriva separata metoder för att räkna upp varje sorts djur? Faktiskt, nej, det gör vi inte :) Och som det händer, jokertecken hjälper oss med detta! Vi kan lösa problemet med en enkel metod med följande konstruktion:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Detta är ett jokertecken. Mer exakt är detta den första av flera typer av jokertecken. Det är känt som jokertecken med övre gräns och uttrycks av ? sträcker sig . Vad säger denna konstruktion oss? Detta innebär att metoden accepterar en samling Animalobjekt eller en samling objekt av vilken klass som helst som härstammar från (? Animalsträcker sig Animal). Med andra ord kan metoden acceptera en samling av Animal, Pet, Dog, eller Catobjekt - det gör ingen skillnad. Låt oss övertyga oss själva om att det fungerar:
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);
}
Konsolutgång:
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!
Vi skapade totalt 4 samlingar och 8 objekt, och det finns exakt 8 poster på konsolen. Allt fungerar utmärkt! :) Jokertecknet gjorde det möjligt för oss att enkelt passa in den nödvändiga logiken kopplad till specifika typer i en enda metod. Vi eliminerade behovet av att skriva en separat metod för varje typ av djur. Föreställ dig hur många metoder vi skulle ha behövt om vår applikation användes av en djurpark eller ett veterinärkontor :) Men låt oss nu titta på en annan situation. Vår arvshierarki förblir oförändrad: klassen på högsta nivån är , Animalmed Petklassen strax under, och klasserna Catoch Dogpå nästa nivå. Nu måste du skriva om iterateAnimals()metoden så att den fungerar med alla typer av djur, förutom hundar . Det vill säga, den borde acceptera Collection<Animal>,Collection<Pet>eller Collection<Car>, men det borde inte fungera med Collection<Dog>. Hur kan vi uppnå detta? Det verkar som att vi återigen står inför möjligheten att skriva en separat metod för varje typ :/ Hur ska vi annars förklara för kompilatorn vad vi vill ska hända? Det är faktiskt ganska enkelt! Återigen kommer jokertecken till vår hjälp här. Men den här gången kommer vi att använda en annan typ av jokertecken — ett jokertecken med lägre gräns , som uttrycks med 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!");
   }
}
Här är principen liknande. Konstruktionen <? super Cat>talar om för kompilatorn att iterateAnimals()metoden kan acceptera som indata en samling Catobjekt eller någon förfader till Catklassen som indata. I det här fallet matchar Catklassen, dess förälder Petoch föräldern till dess förälder, , alla denna beskrivning. AnimalKlassen Dogmatchar inte vår begränsning, så ett försök att använda metoden med ett List<Dog>argument kommer att resultera i ett kompileringsfel:
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);
}
Vi har löst vårt problem, och återigen visade sig jokertecken vara extremt användbara :) Med detta har lektionen tagit slut. Nu ser du hur viktiga generika är i din studie av Java — vi har haft fyra hela lektioner om dem! Men nu är du väl insatt i ämnet och du kan bevisa dina färdigheter i anställningsintervjuer :) Och nu är det dags att återgå till uppgifterna! Lycka till med dina studier! :)
Kommentarer
  • Populär
  • Ny
  • Gammal
Du måste vara inloggad för att lämna en kommentar
Den här sidan har inga kommentarer än