CodeGym /Java blog /Véletlen /Helyettesítő karakterek a generikus kifejezésekben
John Squirrels
Szint
San Francisco

Helyettesítő karakterek a generikus kifejezésekben

Megjelent a csoportban
Szia! Folytassuk a generikumok tanulmányozását. A korábbi leckékből már jelentős ismereteket szereztél róluk ( a varragok használatáról generikumok használatakor és a típustörlésről ), de van egy fontos téma, amelyet még nem vettünk figyelembe: a helyettesítő karakterek . Ez a generikumok nagyon fontos jellemzője. Olyannyira, hogy külön leckét szenteltünk neki! Ennek ellenére nincs semmi különösebben bonyolult a helyettesítő karakterekben. Azonnal látni fogod :) Nézzünk Helyettesítő karakterek az általánosban – 1egy példát:

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;
   }
}
Mi folyik itt? Két nagyon hasonló helyzetet látunk. Ebben az esetben tárgyat öntünk Stringegy Objecttárgyra. Itt nincs probléma – minden a várt módon működik. De a második helyzetben a fordító hibát generál. De ugyanazt csináljuk, nem? Ezúttal egyszerűen több objektum gyűjteményét használjuk. De miért fordul elő a hiba? Mi a különbség? Egy tárgyat öntünk Stringegy Objectvagy 20 tárgyra? Van egy fontos különbség az objektum és az objektumok gyűjteménye között . Ha az Bosztály az osztály gyermeke A, akkor Collection<B>nem az osztály gyermeke Collection<A>. Ezért nem tudtuk leadni List<String>aList<Object>. Stringgyermeke Object, de List<String>nem gyermeke List<Object>. Ez nem tűnik túl intuitívnak. Miért csinálták így a nyelv alkotói? Képzeljük el, hogy a fordító nem ad nekünk hibát:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
Ebben az esetben például a következőket tehetjük:

objects.add(new Object());
String s = strings.get(0);
Mivel a fordító nem adott nekünk hibát, és lehetővé tette, hogy hivatkozást hozzunk létre, List<Object>amely a -ra mutat strings, ezért bármilyen régi objektumot hozzáadhatunk Objecta stringsgyűjteményhez! Így elvesztettük a garanciát arra vonatkozóan, hogy gyűjteményünk csak Stringaz általános típushívásban a type argumentum által meghatározott objektumokat tartalmazza . Más szóval, elvesztettük a generikumok fő előnyét – a típusbiztonságot. És mivel a fordító nem akadályozott meg ebben, csak futási időben kapunk hibát, ami mindig sokkal rosszabb, mint egy fordítási hiba. Az ehhez hasonló helyzetek elkerülése érdekében a fordító hibát jelez:

// Compilation error
List<Object> objects = strings;
...és emlékeztet bennünket, hogy List<String>nem leszármazottja a List<Object>. Ez egy vaskalapos szabály a generikus gyógyszerek esetében, és emlékezni kell rá, amikor velük dolgozik. Menjünk tovább. Tegyük fel, hogy van egy kis osztályhierarchiánk:

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()");
   }
}
A hierarchiát egy egyszerű Animal osztály vezeti, amelyet Pet örökölt. A kisállatnak 2 alosztálya van: kutya és macska. Most tegyük fel, hogy létre kell hoznunk egy egyszerű iterateAnimals()módszert. A metódusnak fel kell vennie egy tetszőleges állatot ( Animal, Pet, Cat, Dog), végig kell iterálnia az összes elemet, és minden iteráció során üzenetet kell megjelenítenie a konzolon. Próbáljunk meg írni egy ilyen módszert:

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

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Úgy tűnik, a probléma megoldódott! Azonban, mint nemrégiben megtudtuk, List<Cat>és List<Dog>nem List<Pet>leszármazottai List<Animal>! Ez azt jelenti, hogy amikor megpróbáljuk meghívni a iterateAnimals()metódust macskák listájával, fordítási hibát kapunk:

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);
   }
}
Nem tűnik túl jónak a helyzet számunkra! Külön módszert kell írnunk az egyes állatfajták számbavételére? Igazából nem, nem :) És ahogy megtörténik, a helyettesítő karakterek segítenek nekünk ebben! A problémát egyetlen egyszerű módszerrel megoldhatjuk a következő konstrukció segítségével:

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

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Ez egy helyettesítő karakter. Pontosabban, ez az első a sokféle helyettesítő karakter közül. Felső korlátos helyettesítő karakterként ismert, és a ? kiterjeszti . Mit mond nekünk ez a konstrukció? Ez azt jelenti, hogy a metódus elfogadja az objektumok gyűjteményét Animalvagy bármely osztályból származó objektumok gyűjteményét Animal(? kiterjeszti az Animal-t). AnimalMás szavakkal, a metódus képes , Pet, Dog, vagy objektumok gyűjteményét fogadni Cat– ez nem számít. Győződjünk meg arról, hogy működik:

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

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!
Összesen 4 gyűjteményt és 8 objektumot hoztunk létre, a konzolon pedig pontosan 8 bejegyzés található. Minden remekül működik! :) A helyettesítő karakter lehetővé tette, hogy az adott típusokhoz kötött szükséges logikát könnyedén egyetlen metódusba illesszük. Kiküszöböltük, hogy minden állatfajra külön módszert írjunk. Képzeld el, mennyi módszerre lett volna szükségünk, ha az alkalmazásunkat egy állatkert vagy egy állatorvosi hivatal használja :) De most nézzünk egy másik helyzetet. Az öröklési hierarchiánk változatlan marad: a legfelső szintű osztály a Animal, az Petosztály közvetlenül alatta, a Catés Dogosztályok pedig a következő szinten. Most át kell írnia a iterateAnimals()módszert, hogy bármilyen típusú állattal működjön, kivéve a kutyákat . Vagyis el kell fogadnia Collection<Animal>,Collection<Pet>vagy Collection<Car>, de nem működhet együtt Collection<Dog>. Hogyan érhetjük el ezt? Úgy tűnik, ismét azzal a lehetőséggel nézünk szembe, hogy minden típushoz külön metódust írjunk :/ Hogyan magyarázzuk el másként a fordítónak, hogy mi történjen? Valójában nagyon egyszerű! Itt ismét a helyettesítő karakterek jönnek a segítségünkre. Ezúttal azonban egy másik típusú helyettesítő karaktert fogunk használni – egy alsó határú helyettesítő karaktert , amelyet a super kifejezéssel fejezünk ki .

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

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

       System.out.println("Another iteration in the loop!");
   }
}
Itt is hasonló az elv. A <? super Cat>konstrukció közli a fordítóval, hogy a iterateAnimals()metódus bemenetként fogadhat objektumok gyűjteményét Catvagy az osztály bármely ősét Catbemenetként. Ebben az esetben az Catosztály, a szülője Petés a szülőjének szülője, Animal, mind megfelel ennek a leírásnak. Az Dogosztály nem egyezik a korlátozásunkkal, így a metódus argumentumokkal történő használatára tett kísérlet List<Dog>fordítási hibát eredményez:

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);
}
Megoldottuk a problémánkat, és a helyettesítő karakterek ismét rendkívül hasznosnak bizonyultak :) Ezzel a lecke véget ért. Most már látja, milyen fontosak a generikumok a Java tanulmányozásában – 4 egész leckét vettünk róluk! De most már jártas vagy a témában, és állásinterjúkon bizonyíthatod tudásodat :) És most itt az ideje, hogy visszatérj a feladatokhoz! Sok sikert a tanuláshoz! :)
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION