JavaRush /Java блогу /Random-KY /Java Genericsдеги Wildcards

Java Genericsдеги Wildcards

Группада жарыяланган
Салам! Биз генериктер темасын изилдөөнү улантабыз. Мурунку лекциялардан алар жөнүндө жакшы маалыматка ээсиз ( генериктер менен иштөөдө varargs колдонуу жана түрүн өчүрүү жөнүндө ), бирок биз азырынча бир маанилүү теманы камтый элекпиз: wildcards . Бул генериктердин абдан маанилүү өзгөчөлүгү. Ушунчалык, биз ага өзүнчө лекция арнадык! Бирок, коймо белгилерде эч кандай татаал эч нерсе жок, сиз муну азыр көрөсүз :) генериктердеги Wildcards - 1Мисалга карап көрөлү:
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // ниHowих проблем
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // ошибка компиляции!
       List<Object> objects = strings;
   }
}
Бул жерде эмне болуп жатат? Биз абдан окшош эки жагдайды көрүп жатабыз. Алардын биринчисинде биз an objectти терүүгө аракет кылып Stringжатабыз Object. Бул боюнча эч кандай көйгөйлөр жок, баары каалагандай иштейт. Бирок экинчи жагдайда компилятор ката кетирет. Биз да ошол эле нерсени кылып жаткандай сезилет да. Болгону азыр биз бир нече an objectтердин коллекциясын колдонуп жатабыз. Бирок эмне үчүн ката пайда болот? Кандай айырма бар, биз бир an objectти Stringтүргө таштайбызбы Objectже 20 an object болобу? Объект менен an objectтердин жыйындысынын ортосунда маанилүү айырма бар . Эгерде класс Bкласстын мураскери болсо А, Collection<B>анда ал мураскер эмес Collection<A>. Ошол себептүү биз өзүбүздүкү алып келе List<String>албадык List<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);
Компилятор бизге каталарды бербегендиктен жана List<Object> objectсаптардын жыйындысына шилтеме түзүүгө уруксат бергендиктен, биз сапты эмес, жөн гана каалаган an objectти stringsкошо алабыз ! Ошентип, биз коллекциябыз жалпы бөлүмдө көрсөтүлгөн an objectтерди гана камтыйт деген кепилдикти жоготтук . Башкача айтканда, биз генериктердин негизги артыкчылыгын жоготтук - типтеги коопсуздук. Ал эми компилятор мунун баарын жасоого мүмкүндүк бергендиктен, бул биз программаны аткаруу учурунда гана ката алабыз дегенди билдирет, бул дайыма компиляция катасынан алда канча начар. Мындай жагдайлардын алдын алуу үчүн, компилятор бизге ката берет: stringsObjectString
// ошибка компиляции
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()");
   }
}
Иерархиянын башында жөн гана Жаныбарлар турат: Үй жаныбарлары алардан тукум кууп өткөн. Үй жаныбарлары 2 түргө бөлүнөт - Иттер жана Мышыктар. Эми жөнөкөй ыкманы түзүшүбүз керек деп элестетиңиз iterateAnimals(). Метод каалаган жаныбарлардын коллекциясын ( Animal, Pet, , Cat, Dog) кабыл алышы керек, бардык элементтерди кайталап, ар бир жолу консолго бир нерсени чыгарышы керек. Бул ыкманы жазууга аракет кылалы:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Маселе чечилди окшойт! Бирок, жакында эле белгилүү болгондой, 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("Еще один шаг в цикле пройден!");
       }
   }

   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());

       //ошибка компилятора!
       iterateAnimals(cats);
   }
}
Бизде абал жакшы эмес! Көрсө, малдын бардык түрүн санап чыгуунун өзүнчө ыкмаларын жазууга туура келет экен? Чындыгында, жок, кереги жок :) Жана бул жагынан бизге штрихтер жардам берет ! Биз төмөнкү курулушту колдонуу менен бир жөнөкөй ыкма менен маселени чечет:
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Бул койнок. Тагыраак айтканда, бул бир нече түрлөрдүн биринчиси болуп саналат - “ узартуу ” (дагы бир аты - Upper Bounded Wildcards ). Бул дизайн бизге эмнени айтып турат? AnimalБул ыкма кириш катары класс an objectтеринин же кандайдыр бир тукум класстын an objectтеринин жыйындысын кабыл алат дегенди билдирет Animal (? extends Animal). AnimalБашка сөз менен айтканда, ыкма чогултуу кабыл алат , Pet, Dogже киргизүү катары Cat- бул эч кандай айырмасы жок. Келгиле, бул иштээрин текшерели:
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);
}
Консолдук чыгаруу:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Биз бардыгы болуп 4 коллекция жана 8 an object түздүк, консолдо так 8 жазуу бар. Баары сонун иштейт! :) Wildcard бизге керектүү логиканы конкреттүү түрлөрдү бириктирүү менен бир методго оңой батырууга мүмкүндүк берди. Малдын ар бир түрү үчүн өзүнчө ыкма жазуудан арылдык. Элестеткиле, эгер биздин тиркеме зоопаркта же ветеринардык клиникада колдонулса, бизде канча ыкма болмок :) Эми башка жагдайды карап көрөлү. Биздин мурас иерархиябыз өзгөрүүсүз калат: жогорку деңгээлдеги класс - Animal, ылдыйда - Үй жаныбарлары классы Pet, ал эми кийинки деңгээлде - Catжана Dog. Эми сиз ыкманы иттерден башкаiretateAnimals() жаныбарлардын бардык түрү менен иштей тургандай кылып кайра жазышыңыз керек . Башкача айтканда, ал , же киргизүү катары кабыл алынышы керек , бирок менен иштебеши керек . бул кантип жетсек болот? Ар бир түр үчүн өзүнчө ыкманы жазуу перспективасы кайрадан алдыбызда турат окшойт :/ Биз компиляторго логикабызды дагы кантип түшүндүрө алабыз? Жана бул абдан жөнөкөй кылса болот! Бул жерде биз үчүн кожоюндар дагы жардамга келет. Бирок бул жолу биз башка түрүн колдонобуз - “ super ” (башка аты Төмөнкү чектеш Wildcards ). Collection<Animal>Collection<Pet>Collection<Cat>Collection<Dog>
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Бул жерде принцип окшош. Конструкция <? super Cat>компиляторго метод iterateAnimals()класстын an objectтеринин жыйындысын Catже башка ата-класстын киришин ала аларын айтат Cat. CatБиздин учурда класстын өзү , анын түпкү атасы - Pets, ата-бабасынын атасы - бул сүрөттөөгө туура келет 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);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
Биздин маселе чечилди, жана дагы бир жолу коймо таңбалар абдан пайдалуу болуп чыкты :) Ушуну менен лекция аяктайт. Эми сиз Java тorн үйрөнүүдө генериктердин темасы канчалык маанилүү экенин көрүп жатасыз - биз ага 4 лекция өткөрдүк! Эми сиз теманы жакшы түшүндүңүз жана интервьюда өзүңүздү далилдей аласыз :) Эми тапшырмаларга кайтууга убакыт келди! Окууңарга ийгorк! :)
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION