Java generics - ทำไม“ ขยาย T” อนุญาต แต่ไม่“ ใช้ T”


306

ฉันสงสัยว่ามีเหตุผลพิเศษใน Java ที่ใช้เสมอ " extends" มากกว่า " implements" เพื่อกำหนดขอบเขตของพารามิเตอร์

ตัวอย่าง:

public interface C {}
public class A<B implements C>{} 

เป็นสิ่งต้องห้าม แต่

public class A<B extends C>{} 

ถูกต้อง. อะไรคือเหตุผลสำหรับสิ่งนั้น?


14
ฉันไม่รู้ว่าทำไมคนคิดว่าคำตอบของ Tetsujin no Oni ตอบคำถามจริงๆ มันเป็นการตอกย้ำข้อสังเกตของ OP โดยใช้ถ้อยคำเชิงวิชาการ แต่ไม่ได้ให้เหตุผลใด ๆ "ทำไมไม่มีเลยimplements" - "เพราะมีเพียงextends"
ThomasR

1
ThomasR: นั่นเป็นเพราะมันไม่ใช่คำถามของ "อนุญาต" แต่ความหมาย: ไม่มีความแตกต่างในวิธีที่คุณจะเขียนประเภทการบริโภคทั่วไปที่มีข้อ จำกัด ไม่ว่าข้อ จำกัด นั้นมาจากอินเทอร์เฟซหรือประเภทบรรพบุรุษ
Tetsujin no Oni

เพิ่มคำตอบ ( stackoverflow.com/a/56304595/4922375 ) พร้อมเหตุผลของฉันทำไมimplementsจะไม่นำอะไรใหม่มาและจะทำให้สิ่งต่าง ๆ ซับซ้อนขึ้นอีก ฉันหวังว่ามันจะเป็นประโยชน์สำหรับคุณ
Andrew Tobilko

คำตอบ:


328

ไม่มีความแตกต่างทางความหมายในภาษาข้อ จำกัด ทั่วไประหว่างคลาส 'นำไปใช้' หรือ 'ขยาย' ความเป็นไปได้ของข้อ จำกัด คือ 'ขยาย' และ 'ยอดเยี่ยม' - นั่นคือเป็นคลาสนี้ที่จะทำงานโดยมอบหมายให้กับอีกคนหนึ่ง (ขยาย) หรือเป็นชั้นที่กำหนดได้จากอันนี้ (super)


@KomodoDave (ฉันคิดว่าหมายเลขถัดจากคำตอบทำเครื่องหมายว่าถูกต้องฉันไม่แน่ใจว่ามีวิธีอื่นที่จะทำเครื่องหมายหรือไม่บางครั้งคำตอบอื่น ๆ อาจมีข้อมูลเพิ่มเติม - เช่นฉันมีปัญหาเฉพาะที่ฉันไม่สามารถแก้ไขและ google ส่งคุณมาที่นี่เมื่อค้นหา) @Tetsujin no Oni (เป็นไปได้ไหมที่จะใช้รหัสเพื่อชี้แจง? thanx :))
ntg

@ เช่นนี้เป็นตัวอย่างที่ดีมากสำหรับคำถามที่กำลังมองหาตัวอย่าง - ฉันจะลิงก์ในความคิดเห็นแทนที่จะฝังคำตอบในจุดนี้ stackoverflow.com/a/6828257/93922
Tetsujin no Oni

1
ฉันคิดว่าเหตุผลก็คืออย่างน้อยฉันก็อยากจะมีสามัญที่สามารถมี Constructor และวิธีการที่สามารถรับคลาสใด ๆ ที่ทั้ง axtends คลาสพื้นฐานและแสดงอินเตอร์เฟสไม่เพียงแค่อินเตอร์เฟสที่ขยายอินเตอร์เฟส จากนั้นให้อินสแตนซ์ของการทดสอบ Genric สำหรับการนำเสนอของอินเทอร์เฟซและมีคลาสจริงที่ระบุเป็นพารามิเตอร์ชนิด เป็นการดีที่ฉันต้องการ class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }หนึ่งต้องการตรวจสอบเวลารวบรวม
peterk

1
@ peterk และคุณจะได้รับกับ RenderableT ที่ขยาย Renderable, Draggable, Droppable .... เว้นแต่ว่าฉันไม่เข้าใจสิ่งที่คุณต้องการให้ยาสามัญประจำการลบออกทำเพื่อคุณโดยที่สิ่งนี้ไม่ได้ให้
Tetsujin no Oni

@TetsujinnoOni คุณไม่ได้อยู่ในสิ่งที่ฉันต้องการสำหรับการบังคับใช้เวลาคอมไพล์ของการยอมรับเฉพาะคลาสที่ใช้อินเทอร์เฟซชุดหนึ่งและจากนั้นสามารถอ้างอิงคลาส OBJECT (ที่แสดงอินเทอร์เฟซเหล่านั้น) ในทั่วไป อย่างน้อยในเวลาคอมไพล์ (และที่น่าปรารถนา ณ รันไทม์) สิ่งใดก็ตามที่กำหนดให้กับทั่วไปสามารถส่งไปยังอินเตอร์เฟสใด ๆ ที่ระบุได้อย่างปลอดภัย นี่ไม่ใช่กรณีที่มีการใช้งานจาวาในขณะนี้ แต่คงเป็นเรื่องดี :)
peterk

77

คำตอบอยู่ที่นี่  :

หากต้องการประกาศพารามิเตอร์ชนิดที่มีขอบเขตให้แสดงรายการชื่อของพารามิเตอร์ประเภทตามด้วยextendsคำหลักตามด้วยขอบเขตด้านบน […] โปรดทราบว่าในบริบทนี้การขยายจะใช้ในความหมายทั่วไปเพื่อหมายถึงextends(เช่นในคลาส) หรือimplements(ในอินเทอร์เฟซ)

คุณก็มีแล้วมันค่อนข้างสับสนและ Oracle ก็รู้


1
เพื่อเพิ่มความสับสนให้getFoo(List<? super Foo> fooList) ใช้ได้กับคลาสที่ขยายโดย Foo เช่นclass Foo extends WildcardClassนั้นเท่านั้น ในกรณีนี้List<WildcardClass>จะเป็นอินพุตที่ยอมรับได้ อย่างไรก็ตามระดับใด ๆ ที่Fooดำเนินการจะไม่ทำงานclass Foo implements NonWorkingWildcardClassไม่ได้หมายความว่าจะถูกต้องในList<NonWorkingWildcardClass> getFoo(List<? super Foo> fooList)ใส!
Ray

19

อาจเป็นเพราะทั้งสองด้าน (B และ C) มีเพียงประเภทเท่านั้นที่เกี่ยวข้องไม่ใช่การนำไปใช้ ในตัวอย่างของคุณ

public class A<B extends C>{}

B สามารถเป็นส่วนต่อประสานได้เช่นกัน "extends" ใช้เพื่อกำหนดอินเทอร์เฟซย่อยรวมถึงคลาสย่อย

interface IntfSub extends IntfSuper {}
class ClzSub extends ClzSuper {}

ฉันมักจะคิดว่า 'Sub ขยาย Super' เป็น ' Subเป็นเหมือนSuperแต่มีความสามารถเพิ่มเติม' และ 'Clz ใช้ Intf' เนื่องจาก ' Clzเป็นสำนึกของIntf ' ในตัวอย่างของคุณสิ่งนี้จะจับคู่: Bเป็นเหมือนCแต่มีความสามารถเพิ่มเติม ความสามารถมีความเกี่ยวข้องที่นี่ไม่ใช่การรับรู้


10
พิจารณา <B ขยาย D & E> E <caps> ต้องไม่ </caps> เป็นคลาส
Tom Hawtin - tackline

7

อาจเป็นได้ว่าประเภทฐานเป็นพารามิเตอร์ทั่วไปดังนั้นประเภทจริงอาจเป็นส่วนต่อประสานของคลาส พิจารณา:

class MyGen<T, U extends T> {

นอกจากนี้จากอินเทอร์เฟซมุมมองรหัสลูกค้าเกือบจะแยกไม่ออกจากคลาสในขณะที่สำหรับชนิดย่อยเป็นสิ่งสำคัญ


7

นี่คือตัวอย่างที่เกี่ยวข้องเพิ่มเติมเกี่ยวกับตำแหน่งที่อนุญาตให้ขยายและอาจเป็นสิ่งที่คุณต้องการ:

public class A<T1 extends Comparable<T1>>


5

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

แต่ฉันคิดว่าimplementsมันสมเหตุสมผลกว่าเล็กน้อย ฉันคิดว่าการสื่อสารเพิ่มเติมว่าประเภทพารามิเตอร์ไม่จำเป็นต้องอยู่ในความสัมพันธ์ในการสืบทอดพวกเขาสามารถอยู่ในความสัมพันธ์ประเภทย่อยใด ๆ

ชวาคำศัพท์เป็นการแสดงออกถึงมุมมองที่คล้ายกัน


3

เราเคยชินกับ

class ClassTypeA implements InterfaceTypeA {}
class ClassTypeB extends ClassTypeA {}

และการเบี่ยงเบนเล็กน้อยจากกฎเหล่านี้ทำให้เราสับสนอย่างมาก

ไวยากรณ์ของชนิดที่ถูกผูกไว้ถูกกำหนดเป็น

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. ประเภทตัวแปร>TypeBound )

หากเราต้องเปลี่ยนเราก็จะเพิ่มimplementsกรณี

TypeBound:
    extends TypeVariable 
    extends ClassType {AdditionalBound}
    implements InterfaceType {AdditionalBound}

และจบลงด้วยคำสั่งที่ประมวลผลเหมือนกันสองข้อ

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. ประเภทและค่าอ้างอิง>ClassOrInterfaceType )

ยกเว้นเราจะต้องดูแลimplementsซึ่งจะทำให้สิ่งต่าง ๆ ยุ่งยากมากขึ้น

ฉันเชื่อว่าเป็นเหตุผลหลักที่ทำไมจึงextends ClassOrInterfaceTypeใช้แทนextends ClassTypeและimplements InterfaceType- เพื่อให้สิ่งต่าง ๆ เรียบง่ายภายใต้แนวคิดที่ซับซ้อน ปัญหาคือเราไม่มีคำที่เหมาะสมที่จะครอบคลุมทั้งสองextendsและimplementsแน่นอนเราไม่ต้องการแนะนำ

<T is ClassTypeA>
<T is InterfaceTypeA>

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


-1

ในความเป็นจริงเมื่อใช้ทั่วไปในอินเตอร์เฟซที่คำหลักนอกจากนี้ยังขยาย นี่คือตัวอย่างรหัส:

มี 2 ​​คลาสที่ใช้อินเตอร์เฟสทักทาย:

interface Greeting {
    void sayHello();
}

class Dog implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Greeting from Dog: Hello ");
    }
}

class Cat implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Greeting from Cat: Hello ");
    }
}

และรหัสทดสอบ:

@Test
public void testGeneric() {
    Collection<? extends Greeting> animals;

    List<Dog> dogs = Arrays.asList(new Dog(), new Dog(), new Dog());
    List<Cat> cats = Arrays.asList(new Cat(), new Cat(), new Cat());

    animals = dogs;
    for(Greeting g: animals) g.sayHello();

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