เหตุใดจึงต้องใช้อินเตอร์เฟสที่ซ้อนกันแบบคงที่ใน Java


235

ฉันเพิ่งพบอินเทอร์เฟซซ้อนแบบคงที่ในรหัสฐานของเรา

class Foo {
    public static interface Bar {
        /* snip */
    }
    /* snip */
}

ฉันไม่เคยเห็นสิ่งนี้มาก่อน นักพัฒนาดั้งเดิมไม่สามารถเข้าถึงได้ ดังนั้นฉันต้องถามดังนั้น:

ความหมายอะไรที่อยู่เบื้องหลังส่วนต่อประสานคงที่ สิ่งที่จะเปลี่ยนถ้าผมเอาstatic? ทำไมทุกคนจะทำเช่นนี้?


2
นี่ไม่ใช่ 'อินเทอร์เฟซภายใน: เป็นอินเทอร์เฟซซ้อนกัน Inner มีความหมายเฉพาะใน Java
มาร์ควิสแห่ง Lorne

คำตอบ:


293

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

ไม่ว่าจะด้วยวิธีใดผู้พัฒนาจะประกาศเพียงอินเทอร์เฟซชื่อ Foo.Bar ไม่มีการเชื่อมโยงเพิ่มเติมกับคลาสที่ปิดล้อมยกเว้นรหัสที่ไม่สามารถเข้าถึง Foo จะไม่สามารถเข้าถึง Foo.Bar ได้เช่นกัน (จากซอร์สโค้ด - ไบต์หรือการสะท้อนสามารถเข้าถึง Foo.Bar แม้ว่า Foo จะเป็นแพ็คเกจส่วนตัว!)

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

public class Foo {
    public interface Bar {
        void callback();
    }
    public static void registerCallback(Bar bar) {...}
}
// ...elsewhere...
Foo.registerCallback(new Foo.Bar() {
    public void callback() {...}
});

1
ในคำตอบของ Jesse Glick สิ่งนี้หมายความว่า: (จากซอร์สโค้ด - ไบต์หรือการสะท้อนสามารถเข้าถึง Foo.Bar แม้ว่า Foo จะเป็นแพ็คเกจส่วนตัว!)
Vasu

Kaillash, วิธีการส่วนตัวสามารถเข้าถึงได้ผ่าน relfection (ในแพคเกจสะท้อน) และผ่านการเข้าถึงโดยตรง bytecode ของไฟล์. class ที่สร้างขึ้น
gmoore

2
โดย "bytecode ... สามารถเข้าถึง Foo.Bar" ฉันหมายถึงคลาสที่คอมไพล์ที่อ้างอิง Foo.Bar สามารถโหลดและเรียกใช้แม้ว่าจะไม่สามารถอ้างอิง Foo ได้ สิ่งนี้อาจเกิดขึ้นหากคลาสถูกคอมไพล์ในเวลาก่อนหน้าเมื่อ Foo เป็นแบบสาธารณะหรือถ้าคลาสนั้นประกอบด้วยมือหรือรวบรวมจากภาษาที่ไม่ใช่ภาษาจาวา ฯลฯ คอมไพเลอร์ Java อย่างไรก็ตามตรวจสอบตัวดัดแปลงการเข้าถึงบนคลาสที่ล้อมรอบ แม้เมื่อไบต์ที่เกิดขึ้นจะไม่อ้างถึงคลาสที่ล้อมรอบนั้น
Jesse Glick

@Jesse คลาสแบบคงที่ส่วนตัวในคลาสระดับบนสุดสามารถเข้าถึงได้ผ่านการสะท้อนกลับหรือไม่
Pacerier

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

72

คำถามที่ได้รับการตอบรับ แต่เหตุผลหนึ่งที่ดีที่จะใช้อินเตอร์เฟซที่ซ้อนกันคือถ้าฟังก์ชั่นของมันจะเกี่ยวข้องโดยตรงกับการเรียนมันอยู่ใน. Listenerตัวอย่างที่ดีของนี้เป็น หากคุณมีชั้นเรียนFooและคุณอยากชั้นเรียนอื่น ๆ เพื่อให้สามารถฟังสำหรับกิจกรรมในนั้นคุณสามารถประกาศอินเตอร์เฟซที่มีชื่อFooListenerซึ่งเป็น ok แต่ก็อาจจะมีความชัดเจนมากขึ้นในการประกาศอินเตอร์เฟซที่ซ้อนกันและมีผู้ที่ชั้นเรียนอื่น ๆ ดำเนินการFoo.Listener( คลาสที่ซ้อนกันFoo.Eventนั้นไม่เลวพร้อมกับสิ่งนี้)


11
ตัวอย่างทั่วไปคือjava.util.Map.Entry(ซึ่งเป็นอินเทอร์เฟซซ้อนอยู่ในอินเทอร์เฟซอื่น)
Paŭlo Ebermann

4
ฉันรู้ว่านี่เป็นหัวข้อเก่า แต่ฉันต้องการให้คลาสภายนอกอยู่ในแพ็คเกจของตัวเองและอินเทอร์เฟซเสริมใด ๆ (เช่นMap.Entry) หรือคลาสที่อยู่ในแพ็คเกจนั้น ฉันพูดแบบนี้เพราะฉันต้องการให้ชั้นเรียนของฉันสั้นและตรงประเด็น นอกจากนี้ผู้อ่านสามารถดูว่าหน่วยงานอื่น ๆ ที่เกี่ยวข้องกับชั้นเรียนโดยดูที่ชั้นเรียนในแพคเกจ ฉันอาจมีแพ็คเกจjava.collections.mapสำหรับแผนที่ นี่เป็นเรื่องเกี่ยวกับ OO และแบบแยกส่วน java.utilมีมากเกินไปในนั้น utilเป็นอย่างไรcommon- กลิ่น IMO
David Kerr

1
@Shaggy: java.utilแน่นอนมีมากเกินไป ที่กล่าวว่าฉันไม่คิดว่าการแบ่งมันออกเป็นแพคเกจที่ละเอียดเหมือนที่คุณแนะนำเป็นอุดมคติเช่นกัน
ColinD

1
@DavidKerr สมมติว่าคุณเรียกใช้อินเทอร์เฟซนี้java.util.MapEntryนอกแพ็คเกจของตัวเอง First reflex: อินเตอร์เฟสที่เกี่ยวข้องกับคลาสนี้คืออะไร? ฉันมองเข้าไปในชั้นเรียน เว้นแต่จะมีการเชื่อมโยง javadoc ไปยังอินเทอร์เฟซนี้ฉันไม่มีเงื่อนงำเกี่ยวกับความสัมพันธ์ของพวกเขา รวมถึงคนไม่ดูแพคเกจการนำเข้า ทุกคนมีความคิดเห็นของตัวเอง ไม่มีใครถูกต้องไม่มีใครผิด
Raymond Chenon

@ RaymondChenon เป็นส่วนหนึ่งของสิ่งที่ฉันพูดคือการใส่ Map และคลาสที่เกี่ยวข้องเช่น Map.Entry ลงในแพ็คเกจอื่นเช่น java.collections.map วิธีนี้โค้ดทั้งหมดที่เกี่ยวข้องกับแผนที่จะอยู่ในที่เดียว (แพ็คเกจ) และคลาส Map ระดับบนสุดไม่ได้รับภาระ ฉันอาจจะใส่การใช้งานที่เกี่ยวข้อง (HashMap ฯลฯ ) ในแพ็คเกจเดียวกัน
David Kerr

14

ส่วนต่อประสานสมาชิกนั้นคงที่โดยปริยาย ตัวดัดแปลงแบบคงที่ในตัวอย่างของคุณสามารถลบออกได้โดยไม่ต้องเปลี่ยนซีแมนทิกส์ของรหัส ดูเพิ่มเติม Java Language Specification 8.5.1 ประกาศประเภทสมาชิกคงที่


นี่ไม่ใช่ 'อินเทอร์เฟซภายใน': เป็นอินเทอร์เฟซซ้อนกัน Inner มีความหมายเฉพาะใน Java
มาร์ควิสแห่ง Lorne

@EJP ฉันอ่านบล็อกต่าง ๆ โดยใช้เงื่อนไขแทนกัน มีอินเทอร์เฟซภายในหรือไม่ แตกต่างจากอินเทอร์เฟซที่ซ้อนกันอย่างไร
หมายเลข 945

@BreakingBenjamin อินเทอร์เฟซซ้อนกันหมายถึงสแตติก มีคลาสภายในและคลาสที่ซ้อนอยู่ แต่ไม่มีอินเตอร์เฟสภายใน แม้ว่าจะเป็นเช่นนั้นควรเรียกว่าเป็นส่วนต่อประสานที่ซ้อนกัน ด้านใน - ไม่คงที่และซ้อนกันเป็นแบบคงที่
Sundar Rajan

9

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

public class Baz implements Foo.Bar {
   ...
}

ในวิธีส่วนใหญ่สิ่งนี้ไม่แตกต่างจากคลาสภายในแบบคงที่


35
อินเทอร์เฟซที่ซ้อนกันจะคงที่โดยอัตโนมัติไม่ว่าใครจะเขียนคำหลัก
Paŭlo Ebermann

3
เราต้องการวิธีในการให้ชุมชนลงคะแนนเพื่อยอมรับคำตอบที่แตกต่าง: stackoverflow.com/a/74400/632951
Pacerier

2
'คงที่ภายใน' ความขัดแย้งในแง่
มาร์ควิสแห่ง Lorne

@ ClintonN.Dreisbach คุณช่วยอธิบายเพิ่มเติมว่าอะไรมีความหมายThe interface isn't associated with instances of the class, but with the class itselfมากกว่านี้ฉันไม่เข้าใจ
Kasun Siyambalapitiya

6

คำตอบของ Jesse นั้นใกล้เคียงกัน แต่ฉันคิดว่ามีรหัสที่ดีกว่าเพื่อแสดงว่าทำไมอินเทอร์เฟซภายในอาจมีประโยชน์ ดูรหัสด้านล่างก่อนที่จะอ่าน คุณสามารถค้นหาสาเหตุที่อินเทอร์เฟซภายในมีประโยชน์หรือไม่ คำตอบคือคลาส DoSomethingAlready สามารถอินสแตนซ์ภายในได้ช่วยให้สามารถสร้างประเภทที่กำหนดเองและปรับปรุงการห่อหุ้มได้คลาสใดก็ได้ที่ใช้ A และ C ไม่ใช่แค่สวนสัตว์ระดับคอนกรีต แน่นอนว่าสิ่งนี้สามารถทำได้แม้ว่า AC จะไม่ได้อยู่ข้างใน แต่ลองนึกภาพการต่อชื่อที่ยาวกว่า (ไม่ใช่แค่ A และ C) และทำสิ่งนี้สำหรับการผสมอื่น ๆ (พูด A และ B, C และ B ฯลฯ ) และคุณได้อย่างง่ายดาย ดูว่าสิ่งต่าง ๆ อยู่นอกเหนือการควบคุม ไม่ต้องพูดถึงว่าคนที่ตรวจสอบซอร์สโค้ดของคุณจะถูกครอบงำด้วยอินเทอร์เฟซที่มีความหมายเฉพาะในหนึ่งคลาสดังนั้นเพื่อสรุป

class ConcreteA implements A {
 :
}

class ConcreteB implements B {
 :
}

class ConcreteC implements C {
 :
}

class Zoo implements A, C {
 :
}

class DoSomethingAlready {
  interface AC extends A, C { }

  private final AC ac;

  DoSomethingAlready(AC ac) {
    this.ac = ac;
  }
}

2
นี่เป็นเรื่องไร้สาระ คลาสZooไม่ได้ใช้อินเตอร์เฟสACดังนั้นอินสแตนซ์ของZooไม่สามารถส่งผ่านไปยังตัวสร้างDoSomethingAlreadyที่ACต้องการ ความจริงที่ว่าACขยายทั้งสองAและCไม่ได้หมายความว่าการดำเนินการเรียนAและน่าอัศจรรย์นอกจากนี้ยังดำเนินการC AC
Holger

3

หากต้องการตอบคำถามของคุณโดยตรงให้ดูที่ Map.Entry

Map.Entry

นอกจากนี้อาจมีประโยชน์

บล็อก Inerfaces ซ้อนกันแบบคงที่


4
นี่คือตัวอย่าง แต่ไม่ใช่คำตอบจริงๆ
Paŭlo Ebermann

ฉันใช้ Map.Entry เพื่อสร้างวัตถุ "จับคู่" เป็นจำนวนมาก มันสัมผัส การใช้งานของ Pair มีสองโรงเรียนแห่งความคิด แต่นั่นไม่ใช่จุดที่นี่ แผนที่อาจจะอยู่ข้างใน แต่ฉันใช้ข้างนอก
Ravindranath Akila

0

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


'Static Inner' เป็นความขัดแย้งในแง่ เรียนซ้อนกันมีทั้งแบบคงที่หรือภายใน
มาร์ควิสแห่ง Lorne

0

หากคุณจะเปลี่ยนคลาส Foo เป็นอินเตอร์เฟส Foo คำหลัก "สาธารณะ" ในตัวอย่างด้านบนจะซ้ำซ้อนเช่นกันเพราะ

อินเตอร์เฟซที่กำหนดไว้ในอินเทอร์เฟซอื่นจะคงที่สาธารณะโดยปริยาย


ไม่ได้ตอบคำถาม
ฝนตก

0

ในปี 1998 Philip Wadler เสนอความแตกต่างระหว่างส่วนต่อประสานแบบคงที่และส่วนต่อประสานแบบไม่คงที่

เท่าที่ฉันเห็นความแตกต่างเพียงอย่างเดียวในการสร้างอินเตอร์เฟสแบบไม่คงที่คือตอนนี้มันสามารถรวมคลาสภายในที่ไม่คงที่ได้ ดังนั้นการเปลี่ยนแปลงจะไม่ทำให้โปรแกรม Java ที่มีอยู่ไม่ถูกต้อง

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

ตัวอย่างของความแตกต่างระหว่างส่วนต่อประสานแบบคงที่และไม่คงที่สามารถดูได้ในโค้ดตัวอย่างของเขา :

// This code does NOT compile
class LangF<This extends LangF<This>> {
    interface Visitor<R> {
        public R forNum(int n);
    }

    interface Exp {
        // since Exp is non-static, it can refer to the type bound to This
        public <R> R visit(This.Visitor<R> v);
    }
}

ข้อเสนอแนะของเขาไม่เคยทำใน Java 1.5.0 ดังนั้นคำตอบอื่น ๆ ทั้งหมดถูกต้อง: ไม่มีความแตกต่างกับส่วนต่อประสานแบบคงที่และไม่คงที่


ควรสังเกตว่าทั้งหมดนี้หมายถึง GJ ซึ่งเป็นโปรเซสเซอร์รุ่นแรก ๆ สำหรับ generics ใน Java
มาร์ควิสแห่ง Lorne

-1

ใน Java ส่วนต่อประสานคงที่ / คลาสอนุญาตให้ส่วนต่อประสาน / คลาสใช้เช่นคลาสระดับบนสุดนั่นคือสามารถประกาศโดยคลาสอื่น ๆ ดังนั้นคุณสามารถทำได้:

class Bob
{
  void FuncA ()
  {
    Foo.Bar foobar;
  }
}

หากไม่มีการคงที่ข้างต้นจะล้มเหลวในการรวบรวม ข้อดีของการทำเช่นนี้คือคุณไม่จำเป็นต้องมีไฟล์ต้นฉบับใหม่เพียงเพื่อประกาศอินเตอร์เฟส นอกจากนี้ยังเชื่อมโยงแถบอินเทอร์เฟซเข้ากับคลาส Foo ด้วยสายตาเนื่องจากคุณต้องเขียน Foo.Bar และบอกเป็นนัยว่าคลาส Foo ทำอะไรกับอินสแตนซ์ของ Foo.Bar

รายละเอียดของประเภทชั้นในชวา


มันจะรวบรวม 'คงที่' ซ้ำซ้อน
มาร์ควิสแห่ง Lorne

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

-6

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

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

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

วิธีการคงที่ดีสำหรับผลตอบแทนครั้งเดียวและการคำนวณอย่างรวดเร็วหรือข้อมูลที่ได้รับง่าย


1
ฉันรู้ว่าความหมายคงที่ ฉันไม่เคยเห็นมาก่อนในอินเทอร์เฟซมาก่อน ดังนั้นคำถามของคุณอยู่นอกหัวข้อ - ขอโทษ
โม

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