JavaRush /จาวาบล็อก /Random-TH /สัญลักษณ์ตัวแทนใน Java Generics

สัญลักษณ์ตัวแทนใน Java Generics

เผยแพร่ในกลุ่ม
สวัสดี! เรายังคงศึกษาหัวข้อเรื่องยาชื่อสามัญต่อไป คุณมีความรู้มากมายเกี่ยวกับสิ่งเหล่านี้จากการบรรยายครั้งก่อน ๆ (เกี่ยวกับการใช้ varargs เมื่อทำงานกับยาชื่อสามัญและเกี่ยวกับการลบประเภท ) แต่เรายังไม่ได้กล่าวถึงหัวข้อสำคัญหนึ่งหัวข้อ: ไวด์การ์ด นี่เป็นคุณลักษณะที่สำคัญมากของยาชื่อสามัญ มากเสียจนเราทุ่มเทการบรรยายแยกต่างหากให้กับมัน! อย่างไรก็ตาม ไม่มีอะไรซับซ้อนเกี่ยวกับไวด์การ์ด คุณจะเห็นว่าตอนนี้ :) สัญลักษณ์แทนในยาชื่อสามัญ - 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;
   }
}
เกิดอะไรขึ้นที่นี่? เราเห็นสถานการณ์ที่คล้ายกันมากสองสถานการณ์ ในตอนแรก เรากำลังพยายามส่งวัตถุStringเพื่อObjectพิมพ์ ไม่มีปัญหากับสิ่งนี้ ทุกอย่างทำงานได้ตามปกติ แต่ในสถานการณ์ที่สอง คอมไพเลอร์แสดงข้อผิดพลาด แม้จะดูเหมือนว่าเรากำลังทำสิ่งเดียวกันก็ตาม ตอนนี้เรากำลังใช้คอลเลกชันของวัตถุหลายชิ้น แต่เหตุใดจึงเกิดข้อผิดพลาด? โดยพื้นฐานแล้วอะไรคือความแตกต่างไม่ว่าเราจะโยนวัตถุหนึ่งชิ้นStringเป็นประเภทObjectหรือวัตถุ 20 ชิ้น? มีความแตกต่างที่สำคัญระหว่างวัตถุและ ชุดของ วัตถุ หากคลาสเป็นผู้สืบทอดของคลาสก็จะไม่ใช่ผู้สืบทอด ด้วยเหตุนี้เราจึงไม่สามารถนำของเราไป เป็นทายาทแต่มิใช่ทายาท โดยสัญชาตญาณสิ่งนี้ดูไม่สมเหตุสมผลมากนัก เหตุใดผู้สร้างภาษาจึงได้รับคำแนะนำจากหลักการนี้ ลองจินตนาการว่าคอมไพเลอร์จะไม่ทำให้เรามีข้อผิดพลาดที่นี่: BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
ในกรณีนี้ เราสามารถทำสิ่งต่อไปนี้ได้:
objects.add(new Object());
String s = strings.get(0);
เนื่องจากคอมไพลเลอร์ไม่ได้ให้ข้อผิดพลาดแก่เราและอนุญาตให้เราสร้างการอ้างอิงList<Object> objectถึงคอลเลกชันของสตริงstringsเราจึงสามารถเพิ่มstringsไม่ใช่สตริง แต่เป็นเพียงวัตถุใด ๆObject! ดังนั้นเราจึงสูญเสียการรับประกันว่ามีเพียงออบเจ็กต์ที่ระบุในแบบทั่วไปเท่านั้นที่อยู่ในคอลเลกชันของStringเรา นั่นคือเราได้สูญเสียข้อได้เปรียบหลักของความปลอดภัยประเภทยาชื่อสามัญ และเนื่องจากคอมไพเลอร์อนุญาตให้เราทำทั้งหมดนี้ได้ นั่นหมายความว่าเราจะได้รับข้อผิดพลาดระหว่างการทำงานของโปรแกรมเท่านั้น ซึ่งแย่กว่าข้อผิดพลาดในการคอมไพล์เสมอ เพื่อป้องกันสถานการณ์ดังกล่าว คอมไพลเลอร์แจ้งข้อผิดพลาดแก่เรา:
// ошибка компиляции
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()ด้วยรายการ cat เราจะได้รับข้อผิดพลาดของคอมไพเลอร์:
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หรือวัตถุของคลาสสืบทอดใด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 รายการ และมีรายการในคอนโซลทั้งหมด 8 รายการ ทุกอย่างใช้งานได้ดี! :) สัญลักษณ์ตัวแทนช่วยให้เราปรับตรรกะที่จำเป็นได้อย่างง่ายดายโดยเชื่อมโยงกับประเภทเฉพาะในวิธีเดียว เราไม่จำเป็นต้องเขียนวิธีการแยกต่างหากสำหรับสัตว์แต่ละประเภท ลองนึกภาพว่าเราจะมีวิธีการกี่วิธีหากใบสมัครของเราถูกนำไปใช้ในสวนสัตว์หรือคลินิกสัตวแพทย์ :) ตอนนี้เรามาดูสถานการณ์อื่นกันดีกว่า ลำดับชั้นการสืบทอดของเราจะยังคงเหมือนเดิม: คลาสระดับบนสุดคือAnimalด้านล่างคือคลาส Pets PetและในระดับถัดไปคือCatและ Dogตอนนี้คุณต้องเขียนวิธีการใหม่iretateAnimals()เพื่อให้สามารถใช้ได้กับสัตว์ทุกประเภทยกเว้นสุนัข Collection<Animal>นั่นคือควรยอมรับ หรือCollection<Pet>เป็นอินพุตCollection<Cat>แต่ไม่ควรทำงานCollection<Dog>กับ เราจะบรรลุเป้าหมายนี้ได้อย่างไร? ดูเหมือนว่าโอกาสในการเขียนวิธีการแยกสำหรับแต่ละประเภทกำลังปรากฏต่อหน้าเราอีกครั้ง :/ เราจะอธิบายตรรกะของเราให้คอมไพเลอร์ฟังได้อย่างไร และสามารถทำได้ง่ายมาก! ไวด์การ์ดจะมาช่วยเหลือเราอีกครั้ง แต่คราวนี้เราจะใช้ประเภทอื่น - “ super ” (อีกชื่อหนึ่งคือLower Bounded Wildcards )
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
หลักการก็คล้ายกันที่นี่ โครงสร้าง<? super Cat>จะบอกคอมไพลเลอร์ว่าวิธีการนี้ สามารถใช้เป็น ชุดiterateAnimals()อินพุตของอ็อบเจ็กต์ของคลาสCatหรือคลาสบรรพบุรุษอื่นๆ ในกรณีของเรา ตัวคลาสเอง , บรรพบุรุษของคลาส - และบรรพบุรุษของบรรพบุรุษ - เหมาะสมกับคำ อธิบายCatนี้ คลาสไม่พอดีกับข้อจำกัดนี้ ดังนั้นการพยายามใช้เมธอดที่มีรายการจะส่งผลให้เกิดข้อผิดพลาดในการคอมไพล์: CatPetsAnimalDogList<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 - เราใช้เวลาบรรยายถึง 4 ครั้ง! แต่ตอนนี้คุณเข้าใจหัวข้อนี้ดีแล้วและจะสามารถพิสูจน์ตัวเองในการสัมภาษณ์ได้ :) และตอนนี้ก็ถึงเวลากลับไปสู่ภารกิจแล้ว! ขอให้โชคดีในการศึกษาของคุณ! :)
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION