CodeGym /Java блог /Случаен /Заместващи символи в генеричните продукти
John Squirrels
Ниво
San Francisco

Заместващи символи в генеричните продукти

Публикувано в групата
здрасти Нека продължим нашето изследване на генеричните лекарства. Вече придобихте значителни знания за тях от предишни уроци (за използването на varargs при работа с генерични и за изтриване на типове ), но има важна тема, която все още не сме разгледали — заместващи символи . Това е много важна характеристика на генериците. Толкова много, че му посветихме отделен урок! Въпреки това няма нищо особено сложно в заместващите знаци. Ще видите това веднага :) Заместващи знаци в генеричните продукти - 1Нека да разгледаме един пример:

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;
   }
}
Какво става тук? Виждаме две много сходни ситуации. В случая ние прехвърляме Stringобект към Objectобект. Тук няма проблеми - всичко работи според очакванията. Но във втората ситуация компилаторът генерира грешка. Но ние правим едно и също нещо, нали? Този път просто използваме колекция от няколко обекта. Но защо възниква грешката? Каква е разликата? Прехвърляме ли един Stringобект към Objector 20 обекта? Има важно разграничение между обект и колекция от обекти . Ако Bкласът е дете на Aкласа, тогава Collection<B>не е дете на Collection<A>. Ето защо не успяхме да хвърлим нашия List<String>към aList<Object>. Stringе дете на Object, но List<String>не е дете на List<Object>. Това може да не изглежда супер интуитивно. Защо създателите на езика са го направor по този начин? Нека си представим, че компилаторът не ни дава грешка:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
В този случай бихме могли например да направим следното:

objects.add(new Object());
String s = strings.get(0);
Тъй като компилаторът не ни даде ниHowва грешка и ни позволи да създадем препратка List<Object>, която сочи към strings, можем да добавим всеки стар Objectобект към stringsколекцията! По този начин загубихме гаранцията, че нашата колекция съдържа само обектите, Stringпосочени от аргумента тип в извикването на генеричен тип . С други думи, ние загубихме основното предимство на генериците - безопасността на типа. И тъй като компилаторът не ни спря да направим това, ще получим грешка само по време на изпълнение, което винаги е много по-лошо от грешка при компorране. За да предотвратим ситуации като тази, компилаторът ни дава грешка:

// Compilation error
List<Object> objects = strings;
...и ни напомня, че List<String>не е потомък на List<Object>. Това е желязно правило за генеричните лекарства и трябва да се помни, когато работите с тях. Да продължим. Да предположим, че имаме малка йерархия на класове:

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()");
   }
}
Йерархията се оглавява от прост клас Animal, който е наследен от Pet. Домашен любимец има 2 подкласа: куче и котка. Сега да предположим, че трябва да създадем прост iterateAnimals()метод. Методът трябва да вземе колекция от всяHowви животни ( Animal, Pet, Cat, Dog), да итерира всички елементи и да показва съобщение на конзолата по време на всяка итерация. Нека се опитаме да напишем такъв метод:

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

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Изглежда, че проблемът е решен! Въпреки това, Howто наскоро научихме, List<Cat>и List<Dog>не List<Pet>са потомци на List<Animal>! Това означава, че когато се опитаме да извикаме iterateAnimals()метода със списък с котки, получаваме грешка при компилация:

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);
   }
}
Ситуацията не изглежда много добра за нас! Трябва ли да пишем отделни методи за изброяване на всеки вид животно? Всъщност не, не го правим :) И Howто се случва, заместващите символи ни помагат с това! Можем да разрешим проблема с един прост метод, използвайки следната конструкция:

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

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Това е заместващ знак. По-точно, това е първият от няколко вида заместващи знаци. Известен е като заместващи символи с горна граница и се изразява с ? разширява се . Какво ни казва тази конструкция? Това означава, че методът приема колекция от Animalобекти or колекция от обекти от всеки клас, който произлиза от Animal(? разширява Animal). С други думи, методът може да приеме колекция от Animal, Pet, Dogor Catобекти — няма meaning. Нека се убедим, че работи:

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);
}
Конзолен изход:

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!
Създадохме общо 4 колекции и 8 обекта, а на конзолата има точно 8 записа. Всичко работи отлично! :) Заместващият знак ни позволи лесно да вместим необходимата логика, свързана с конкретни типове, в един метод. Премахнахме необходимостта да пишем отделен метод за всеки тип животно. Представете си колко много методи щяхме да имаме нужда, ако приложението ни се използваше от зоологическа градина or ветеринарен кабинет :) Но сега нека да разгледаме друга ситуация. Нашата йерархия на наследяване остава непроменена: класът от най-високо ниво е Animal, с Petкласа точно под него и класовете Catи Dogна следващото ниво. Сега трябва да пренапишете iterateAnimals()метода, така че да работи с всяHowъв вид животни, с изключение на кучета . Тоест трябва да приеме Collection<Animal>,Collection<Pet>or Collection<Car>, но не трябва да работи с Collection<Dog>. Как можем да постигнем това? Изглежда, че отново сме изпequalsи пред перспективата да пишем отделен метод за всеки тип :/ Как иначе да обясним на компилатора Howво искаме да се случи? Всъщност е доста просто! Тук отново ни идват на помощ заместващите знаци. Но този път ще използваме друг тип заместващ знак — долно ограничен заместващ знак , който се изразява с помощта на супер .

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

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

       System.out.println("Another iteration in the loop!");
   }
}
Тук принципът е подобен. Конструкцията <? super Cat>казва на компилатора, че iterateAnimals()методът може да приеме като вход колекция от Catобекти or всеки предшественик на Catкласа като вход. В този случай Catкласът, неговият родител Petи родителят на неговия родител, Animalвсички отговарят на това описание. Класът Dogне отговаря на нашето ограничение, така че опитът за използване на метода с List<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);
}
Решихме проблема си и отново заместващите символи се оказаха изключително полезни :) С това урокът приключи. Сега виждате колко важни са генериците в изучаването на Java — имахме цели 4 урока за тях! Но вече сте добре запознати с темата и можете да докажете уменията си на интервюта за работа :) А сега е време да се върнем към задачите! Желая успех в обучението! :)
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION