Java - ใช้ความหลากหลายหรือพารามิเตอร์ประเภทที่ถูกผูกไว้


17

สมมติว่าฉันมีลำดับชั้นเรียนนี้ ...

public abstract class Animal {
    public abstract void eat();
    public abstract void talk();
}
class Dog extends Animal {
    @Override
    public void eat() {
    }

    @Override
    public void talk() {
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
    }

    @Override
    public void talk() {
    }
}

แล้วฉันมี ....

public static <T extends Animal> void addAnimal(T animal) {
    animal.eat();
    animal.talk();
}

public static void addAnimalPoly(Animal animal) {
    animal.eat();
    animal.talk();
}

อะไรคือความแตกต่างเมื่อใช้พารามิเตอร์ประเภทที่มีขอบเขตหรือความหลากหลาย?

และเมื่อใดที่จะใช้อย่างใดอย่างหนึ่ง?


1
คำจำกัดความทั้งสองนี้ไม่ได้ผลกำไรมากนักจากพารามิเตอร์ประเภท แต่ลองเขียนaddAnimals(List<Animal>)และเพิ่ม List of Cats!
Kilian Foth

7
ตัวอย่างเช่นถ้าแต่ละวิธีข้างต้นของคุณจะคืนค่าบางอย่างเช่นกันแล้ววิธีการใช้ยาสามัญสามารถคืน T ในขณะที่อีกวิธีหนึ่งสามารถคืนสัตว์ได้ ดังนั้นสำหรับคนที่ใช้วิธีการเหล่านั้นในกรณีแรกเขาจะได้สิ่งที่เขาต้องการกลับคืน: Dod dog = addAnimal (new Dog ()); ในขณะที่ใช้วิธีที่สองเขาจะถูกบังคับให้ต้องโยนเพื่อรับสุนัข: Dog d = (สุนัข) addAnimalPoly (สุนัขใหม่ ());
Shivan Dragon

การใช้งานประเภทที่ จำกัด ของฉันส่วนใหญ่เป็นพื้นวอลล์เปเปอร์ผ่านการใช้งาน generics ที่ไม่ดีของ Java (รายการ <T> มีกฎความแปรปรวนที่แตกต่างกันสำหรับ T เพียงอย่างเดียว) ในกรณีนี้มีข้อได้เปรียบที่ไม่เป็นหลักสองวิธีในการแสดงแนวคิดเดียวกัน แต่เป็น @ShivanDragon กล่าวว่ามันไม่ได้หมายความว่าคุณจะมีเสื้อที่ compiletime แทนที่จะเป็นสัตว์ คุณสามารถปฏิบัติต่อมันเหมือนสัตว์ภายในได้ แต่คุณสามารถให้มันเป็น T ภายนอกได้
Phoshi

คำตอบ:


14

ตัวอย่างสองตัวอย่างนี้มีค่าเท่ากันและในความเป็นจริงจะรวมเป็นรหัสไบต์เดียวกัน

มีสองวิธีที่การเพิ่มประเภททั่วไปที่ถูกผูกไว้กับวิธีการในตัวอย่างแรกของคุณจะทำอะไร

ผ่านพารามิเตอร์ประเภทไปยังประเภทอื่น

ลายเซ็นวิธีการทั้งสองนี้จบลงด้วยการเหมือนกันในรหัสไบต์ แต่คอมไพเลอร์บังคับใช้ความปลอดภัยของประเภท:

public static <T extends Animal> void addAnimals(Collection<T> animals)

public static void addAnimals(Collection<Animal> animals)

ในกรณีแรกเท่านั้นCollection(หรือชนิดย่อย) ของAnimalที่ได้รับอนุญาต ในกรณีที่สองอนุญาตให้Collection(หรือชนิดย่อย) ที่มีประเภททั่วไปAnimalหรือประเภทย่อยได้รับอนุญาต

ตัวอย่างเช่นอนุญาตสิ่งต่อไปนี้ในวิธีแรก แต่ไม่ใช่วิธีที่สอง:

List<Cat> cats = new ArrayList<Cat>();
cats.add(new Cat());
addAnimals(cats);

เหตุผลคือสัตว์ตัวที่สองอนุญาตให้สะสมสัตว์ได้เท่านั้นส่วนสัตว์ตัวที่สองนั้นอนุญาตให้เก็บวัตถุใด ๆ ที่สามารถกำหนดให้กับสัตว์ได้ (เช่นชนิดย่อย) โปรดทราบว่าหากรายการนี้เป็นรายการสัตว์ที่เกิดขึ้นกับแมววิธีใดวิธีหนึ่งก็ยอมรับได้: ปัญหาคือข้อกำหนดทั่วไปของคอลเลกชันไม่ใช่สิ่งที่มีอยู่จริง

ส่งคืนวัตถุ

ในเวลาอื่นมันเป็นเรื่องกับวัตถุกลับ ให้เราสมมติว่ามีวิธีการดังต่อไปนี้:

public static <T extends Animal> T feed(T animal) {
  animal.eat();
  return animal;
}

คุณจะสามารถทำสิ่งต่อไปนี้ได้:

Cat c1 = new Cat();
Cat c2 = feed(c1);

ในขณะที่นี่เป็นตัวอย่างที่วางแผนไว้มีหลายกรณีที่เหมาะสม หากไม่มียาชื่อสามัญวิธีจะต้องกลับมาAnimalและคุณจะต้องเพิ่มการคัดเลือกนักแสดงเพื่อให้ทำงานได้ (ซึ่งเป็นสิ่งที่คอมไพเลอร์เพิ่มลงในรหัสไบต์ต่อไปอยู่ด้านหลัง)


" ในกรณีแรกอนุญาตให้มีการสะสมสัตว์ (หรือชนิดย่อย) ของสัตว์ในกรณีที่สองอนุญาตให้มีการสะสม (หรือชนิดย่อย) ที่มีสัตว์หรือชนิดย่อยทั่วไปได้ " - คุณต้องการเพิ่ม ตรวจสอบตรรกะของคุณใช่ไหม
คดีกองทุนของโมนิกา

3

ใช้ข้อมูลทั่วไปแทนการลดระดับ "Downcasting" แย่มากจากประเภททั่วไปไปเป็นประเภทที่เฉพาะเจาะจงมากขึ้น:

Animal a = hunter.captureOne();
Cat c = (Cat)a;  // ACK!!!!!! What if it's a Dog? ClassCastException!

... คุณเชื่อว่านั่นaคือแมว แต่คอมไพเลอร์ไม่สามารถรับประกันได้ มันอาจกลายเป็นสุนัขที่รันไทม์

ที่นี่คุณจะใช้ยาชื่อสามัญ:

public class <A> Hunter() {
    public A captureOne() { ... }
}

ตอนนี้คุณสามารถระบุว่าคุณต้องการนักล่าแมว:

Hunter<Cat> hunterC = new Hunter<Cat>();
Cat c = hunterC.captureOne();

Hunter<Dog> hunterD = new Hunter<Dog>();
Dog d = hunterD.captureOne();

ตอนนี้คอมไพเลอร์สามารถรับประกันได้ว่า hunterC จะจับแมวเท่านั้นและ hunterD จะจับสุนัขเท่านั้น

ดังนั้นให้ใช้พหุสัณฐานปกติถ้าคุณต้องการจัดการคลาสเฉพาะเป็นประเภทฐาน การ upcasting เป็นสิ่งที่ดี แต่ถ้าคุณอยู่ในสถานการณ์ที่คุณจำเป็นต้องจัดการคลาสที่เฉพาะเจาะจงตามประเภทของตัวเองโดยทั่วไปให้ใช้ generics

หรือจริงๆถ้าคุณพบว่าคุณต้องเศร้าซึมแล้วใช้ยาสามัญ

แก้ไข: กรณีทั่วไปมากขึ้นคือเมื่อคุณต้องการที่จะชะลอการตัดสินใจของชนิดของประเภทที่จะจัดการ ดังนั้นประเภทจึงกลายเป็นพารามิเตอร์เช่นเดียวกับค่า

สมมติว่าฉันต้องการให้ชั้น Zoo ของฉันจัดการกับ Cats หรือ Sponges ฉันไม่มีซุปเปอร์คลาสธรรมดา แต่ฉันยังสามารถใช้:

public class <T> Zoo() { ... }

Zoo<Sponge> spongeZoo = ...
Zoo<Cat> catZoo = ...

ระดับที่คุณล็อคสิ่งนั้นขึ้นอยู่กับสิ่งที่คุณพยายามทำ;)


2

คำถามนี้เป็นคำถามเก่า แต่ปัจจัยสำคัญที่ต้องพิจารณาดูเหมือนว่าจะถูกทิ้งไว้เกี่ยวกับเวลาที่จะใช้ polymorphism เทียบกับพารามิเตอร์ประเภทขอบเขต ปัจจัยนี้อาจสัมผัสกันเล็กน้อยกับตัวอย่างที่ให้ไว้ในคำถาม แต่ฉันรู้สึกว่ามีความเกี่ยวข้องมากกับ "เมื่อใดที่จะใช้ polymorphism กับพารามิเตอร์ประเภทที่มีขอบเขต จำกัด "

TL; DR

หากคุณพบว่าตัวเองกำลังเคลื่อนย้ายโค้ดจากคลาสย่อยไปยังคลาสพื้นฐานกับการตัดสินที่ดีขึ้นของคุณเนื่องจากการไม่สามารถเข้าถึงได้ในแบบ polymorphic พารามิเตอร์ชนิด bounded อาจเป็นโซลูชันที่มีศักยภาพ

คำตอบแบบเต็ม

พารามิเตอร์ชนิดที่ถูก จำกัด สามารถเปิดเผยวิธีการคลาสย่อยที่ไม่สืบทอดสำหรับตัวแปรสมาชิกที่สืบทอด ความแตกต่างไม่สามารถ

หากต้องการอธิบายอย่างละเอียดโดยขยายตัวอย่างของคุณ:

public abstract class AnimalOwner<T extends Animal> {
   protected T pet;
   public abstract void rewardPet();
}

// Modify the dog class
class Dog extends Animal {
   // ...
   // This method is not inherited from anywhere!
   public void scratchBelly() {
      System.out.println("Belly: Scratched");
   }
}

class DogOwner extends AnimalOwner<Dog> {
   DogOwner(Dog dog) {
     this.pet = dog;
   }

   @Override
   public void rewardPet()
   {
      // ---- Note this call ----
      pet.scratchBelly();
   }
}

ถ้า AnimalOwner ระดับนามธรรมถูกกำหนดให้มี a protected Animal pet;และเลือก polymorphism คอมไพเลอร์จะเกิดข้อผิดพลาดบนpet.scratchBelly();บรรทัดบอกคุณว่าวิธีนี้ไม่ได้กำหนดไว้สำหรับสัตว์


1

ในตัวอย่างของคุณคุณไม่ควรใช้ชนิดที่มีขอบเขต ใช้พารามิเตอร์ประเภทที่มีขอบเขตเมื่อคุณต้องเท่านั้นเนื่องจากมีความสับสนที่จะเข้าใจ

นี่คือบางสถานการณ์ที่คุณจะใช้พารามิเตอร์ชนิดที่มีขอบเขต:

  • พารามิเตอร์คอลเลกชัน

    class Zoo {
    
      private List<Animal> animals;
    
      public void add(Collection<? extends Animal> newAnimals) {
        animals.addAll(newAnimals);
      }
    }

    จากนั้นคุณสามารถโทร

    List<Dog> dogs = ...
    zoo.add(dogs);

    zoo.add(dogs)จะล้มเหลวในการรวบรวมโดยไม่ต้อง<? extends Animal>เพราะ generics จะไม่แปรปรวน

  • subclassing

    abstract class Warrior<T extends Weapon> {
    
      public abstract T getWeapon();
    }

    เพื่อ จำกัด ประเภทคลาสย่อยที่สามารถให้ได้

คุณยังสามารถใช้หลาย ๆ ขอบเขต<T extends A1 & A2 & A3>เพื่อให้แน่ใจว่าประเภทเป็นประเภทย่อยของทุกประเภทในรายการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.