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 :)
Lå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 String
objekt till ett Object
objekt. 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 String
objekt till ett Object
eller 20 objekt? Det finns en viktig skillnad mellan ett objekt och en samling objekt . Om B
klassen är ett barn i A
klassen, 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 Object
objekt som helst till strings
samlingen! Därmed har vi förlorat garantin att vår samling endast innehåller de String
objekt 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 Animal
objekt eller en samling objekt av vilken klass som helst som härstammar från (? Animal
sträcker sig Animal). Med andra ord kan metoden acceptera en samling av Animal
, Pet
, Dog
, eller Cat
objekt - 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 , Animal
med Pet
klassen strax under, och klasserna Cat
och Dog
på 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 Cat
objekt eller någon förfader till Cat
klassen som indata. I det här fallet matchar Cat
klassen, dess förälder Pet
och föräldern till dess förälder, , alla denna beskrivning. Animal
Klassen Dog
matchar 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! :)
GO TO FULL VERSION