CodeGym /จาวาบล็อก /สุ่ม /สัญลักษณ์แทนในชื่อสามัญ
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วัตถุหนึ่งชิ้นไปยังวัตถุ 1 Objectหรือ 20 ชิ้นหรือไม่ มีความแตกต่างที่สำคัญระหว่างวัตถุ และคอลเลกชันของวัตถุ หากBชั้นเรียนเป็นลูกของAชั้นเรียน แสดงว่าCollection<B>ไม่ใช่ลูกCollection<A>ของ นี่คือเหตุผลที่เราไม่สามารถโยนของเราList<String>ไปที่List<Object>. Stringเป็นลูกของObjectแต่List<String>ไม่ใช่ลูกList<Object>ของ สิ่งนี้อาจดูไม่ง่ายนัก เหตุใดผู้สร้างภาษาจึงทำเช่นนี้ ลองจินตนาการว่าคอมไพเลอร์ไม่ได้แสดงข้อผิดพลาด:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
ในกรณีนี้ เราสามารถทำได้ดังต่อไปนี้:

objects.add(new Object());
String s = strings.get(0);
เนื่องจากคอมไพเลอร์ไม่ได้ให้ข้อผิดพลาดใด ๆ แก่เราและอนุญาตให้เราสร้างList<Object>การอ้างอิงที่ชี้ไปที่stringsเราสามารถเพิ่มObjectวัตถุเก่า ๆ ลงในstringsคอลเลกชันได้! ดังนั้นเราจึงสูญเสียการรับประกันว่าคอลเลกชันของเรามีเฉพาะStringวัตถุที่ระบุโดยอาร์กิวเมนต์ประเภทในการเรียกใช้ประเภททั่วไป กล่าวอีกนัยหนึ่ง เราได้สูญเสียข้อได้เปรียบหลักของยาชื่อสามัญ — ความปลอดภัยประเภท และเนื่องจากคอมไพเลอร์ไม่ได้หยุดเราจากการทำเช่นนี้ เราจะได้รับข้อผิดพลาดเฉพาะในขณะรันไทม์ ซึ่งแย่กว่าข้อผิดพลาดในการคอมไพล์เสมอ เพื่อป้องกันสถานการณ์เช่นนี้ คอมไพเลอร์แจ้งข้อผิดพลาดแก่เรา:

// 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()");
   }
}
ลำดับชั้นนั้นถูกเติมด้วยคลาสสัตว์ธรรมดาซึ่งสืบทอดมาจากสัตว์เลี้ยง สัตว์เลี้ยงมี 2 คลาสย่อย: สุนัขและแมว สมมติว่าเราต้องสร้างiterateAnimals()วิธีการ ง่ายๆ เมธอดควรรวบรวมสัตว์ใดๆ ( Animal, Pet, Cat, Dog) วนซ้ำองค์ประกอบทั้งหมด และแสดงข้อความบนคอนโซลระหว่างการวนซ้ำแต่ละครั้ง ลองเขียนวิธีการดังกล่าว:

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

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
ดูเหมือนว่าปัญหาจะได้รับการแก้ไข! อย่างไรก็ตาม อย่างที่เราเพิ่งเรียนรู้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);
   }
}
สถานการณ์ดูไม่สู้ดีนักสำหรับเรา! เราต้องเขียนแยกวิธีแจกแจงสัตว์แต่ละชนิดหรือไม่? จริงๆ แล้ว ไม่ เราไม่ทำ :) และเมื่อมันเกิดขึ้นไวลด์การ์ดก็ช่วยเราได้! เราสามารถแก้ปัญหาด้วยวิธีง่าย ๆ หนึ่งวิธีโดยใช้โครงสร้างต่อไปนี้:

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

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
นี่คือตัวแทน แม่นยำยิ่งขึ้น นี่เป็นสัญลักษณ์ตัวแทนประเภทแรกจากหลายประเภท เป็นที่รู้จักกันว่าเป็นสัญลักษณ์แทนขอบเขตบนและแสดงโดย? ขยาย _ โครงสร้างนี้บอกอะไรเรา ซึ่งหมายความว่าเมธอดยอมรับชุดของAnimalวัตถุหรือชุดของวัตถุของคลาสใด ๆ ที่สืบเชื้อสายมาจาก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);
}
เอาต์พุตคอนโซล:

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 รายการบนคอนโซล ทุกอย่างใช้งานได้ดี! :) ไวด์การ์ดช่วยให้เราสามารถใส่ตรรกะที่จำเป็นซึ่งเชื่อมโยงกับประเภทเฉพาะลงในวิธีเดียวได้อย่างง่ายดาย เราไม่จำเป็นต้องเขียนวิธีการแยกสำหรับสัตว์แต่ละประเภท ลองนึกดูว่าเราต้องการวิธีการกี่วิธีหากสวนสัตว์หรือสำนักงานสัตวแพทย์ใช้ใบสมัครของเรา :) แต่ตอนนี้มาดูสถานการณ์ที่ต่างออกไป ลำดับชั้นการสืบทอดของเรายังคงไม่เปลี่ยนแปลง: คลาสระดับบนสุดคือAnimal, โดยมีPetคลาสอยู่ด้านล่าง และ คลาส Catand Dogอยู่ในระดับถัดไป ตอนนี้คุณต้องเขียนiterateAnimals()วิธีการใหม่เพื่อให้ใช้ได้กับสัตว์ทุกประเภทยกเว้นสุนัข นั่นคือควรยอมรับCollection<Animal>,Collection<Pet>หรือCollection<Car>แต่ไม่ควรทำงานCollection<Dog>กับ เราจะบรรลุสิ่งนี้ได้อย่างไร? ดูเหมือนว่าเราจะเผชิญกับโอกาสที่จะเขียนเมธอดแยกต่างหากสำหรับแต่ละประเภทอีกครั้ง :/ เราจะอธิบายให้คอมไพเลอร์ฟังได้อย่างไรว่าเราต้องการให้เกิดขึ้นอย่างไร มันค่อนข้างง่ายจริงๆ! เป็นอีกครั้งที่ wildcards มาช่วยเราที่นี่ แต่คราวนี้เราจะใช้ไวด์การ์ดอีกประเภทหนึ่ง — ไวด์การ์ดที่มี ขอบเขต ต่ำกว่าซึ่งแสดงโดยใช้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!");
   }
}
หลักการนี้คล้ายกัน โครงสร้าง<? super Cat>บอกคอมไพเลอร์ว่าiterateAnimals()เมธอดสามารถรับชุดของCatวัตถุหรือบรรพบุรุษของ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