คลาสซ้อนแบบคงที่ใน Java ทำไม


217

ฉันดูโค้ด Java LinkedListและสังเกตว่ามันใช้คลาสที่ซ้อนกันแบบคงที่Entry .

public class LinkedList<E> ... {
...

 private static class Entry<E> { ... }

}

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

เหตุผลเดียวที่ฉันคิดได้ก็คือรายการนั้นไม่มีการเข้าถึงตัวแปรอินสแตนซ์ดังนั้นจากมุมมอง OOP มันมีการห่อหุ้มที่ดีกว่า

แต่ฉันคิดว่าอาจมีเหตุผลอื่น ๆ บางทีอาจจะเป็นการแสดง มันจะเป็นอะไร?

บันทึก. ฉันหวังว่าฉันมีเงื่อนไขของฉันถูกต้องฉันจะได้เรียกมันว่าเป็นชั้นในแบบคงที่ แต่ฉันคิดว่านี่เป็นสิ่งที่ผิด: http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html


คำตอบ:


271

หน้าอาทิตย์ที่คุณเชื่อมโยงมีความแตกต่างที่สำคัญระหว่างสองอย่างนี้:

คลาสที่ซ้อนกันเป็นสมาชิกของคลาสที่ล้อมรอบ คลาสที่ซ้อนกันแบบไม่คงที่ (คลาสภายใน) มีการเข้าถึงสมาชิกคนอื่น ๆ ของคลาสที่ปิดล้อมแม้ว่าพวกเขาจะถูกประกาศเป็นส่วนตัว คลาสที่ซ้อนกันแบบสแตติกไม่สามารถเข้าถึงสมาชิกอื่นของคลาสที่ปิดล้อม
...

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

ไม่มีความจำเป็นในการเป็นLinkedList.Entryที่จะเป็นระดับระดับบนสุดมันเป็นเพียงใช้โดยLinkedList(มีบาง interfaces อื่น ๆ ที่ยังมีชั้นเรียนที่ซ้อนกันแบบคงที่ชื่อEntryเช่นMap.Entry- แนวคิดเดียวกัน) และเนื่องจากมันไม่จำเป็นต้องเข้าถึงสมาชิกของ LinkedList มันทำให้รู้สึกว่ามันคงที่ - มันเป็นวิธีที่สะอาดกว่ามาก

ในฐานะที่เป็นJon Skeet ชี้ให้เห็นว่าฉันคิดว่ามันเป็นความคิดที่ดีกว่าถ้าคุณใช้คลาสซ้อนกันเพื่อเริ่มต้นกับมันเป็นแบบสแตติกแล้วตัดสินใจว่าจริงๆแล้วมันจะต้องไม่คงที่ตามการใช้งานของคุณ


Bah, ฉันไม่สามารถดูเหมือนจะได้รับการเชื่อมโยงยึดเพื่อแสดงความคิดเห็นในการทำงาน แต่ความคิดเห็นของตนนี้:#comment113712_253507
ซัค Lysobey

1
@matt b หากคลาสที่ซ้อนกันแบบสแตติกไม่สามารถเข้าถึงสมาชิกอินสแตนซ์ของคลาสภายนอกได้อย่างไรมันจะโต้ตอบกับสมาชิกอินสแตนซ์ของคลาสภายนอกได้อย่างไร
Geek

1
@mattb แต่วิธีที่ @Geek สังเกตเห็นหน้า Sun นั้นขัดแย้งกัน: A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class เป็นไปได้อย่างไรถ้าย่อหน้าก่อนเอกสารบอกว่า: Static nested classes do not have access to other members of the enclosing class บางทีพวกเขาอาจต้องการพูดว่า: A nested (non-static) class interacts with the instance members of its outer class (and other classes) just like any other top-level class
tonix

1
@ David ขอบคุณสำหรับการเชื่อมโยง! ใช่ฉันผิดอ่านความคิดเห็นของฉันตอนนี้ฉันเห็นว่าถ้อยคำของฉันไม่ถูกต้อง ในขณะที่คุณกล่าวว่า: An inner class interacts with the instance members through an implicit reference to its enclosing classและจุดนี้ออกมาอีกคุณสมบัติที่น่าสนใจของnon-static inner classesเช่นเดียวกับanonymous inner classesหรือlocal classes defined inside a block: พวกเขาทั้งหมดไม่สามารถมีno-argคอนสตรัสาเหตุเรียบเรียงโดยปริยายจะย่อหน้าลำดับหาเรื่องของทุกคอนสตรัคเพื่อที่จะผ่านการอ้างอิงของตัวอย่างของการปิดล้อมที่ ชั้น ค่อนข้างง่าย
tonix

1
คุณสามารถใช้คลาสภายในแบบคงที่เพื่อยกตัวอย่างคลาสภายนอกซึ่งมีคอนสตรัคเตอร์ส่วนตัวเท่านั้น ใช้ในรูปแบบของเครื่องสร้าง คุณไม่สามารถทำเช่นเดียวกันกับชั้นใน
seenimurugan

47

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

โปรดทราบว่าฉันลำเอียงในฐานะแฟน C # - C # ไม่มีชั้นในเทียบเท่าแม้ว่ามันจะมีประเภทซ้อนกัน ฉันยังบอกไม่ได้ว่าฉันไม่ได้เรียนในชั้นเรียน :)


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

ใช่โคลินขวา - C # ไม่มีคลาสภายในมีคลาสซ้อนกัน ใจคุณชั้นที่ซ้อนกันแบบคงที่ใน C # จะไม่เหมือนกันเป็นชั้นซ้อนกันแบบคงที่ใน Java :)
จอนสกีต

2
ประเภทที่ซ้อนกันเป็นหนึ่งในพื้นที่เหล่านั้นที่ C # ได้รับมันถูกต้องมากเมื่อเทียบกับ Java ฉันมักจะประหลาดใจกับความถูกต้องทางตรรก / ความหมายของมัน ..
nawfal

3
@nawfal: ใช่ยกเว้นไม่กี่ niggles ฉันกลัวว่า C # ภาษาได้รับการออกแบบ (และระบุ)
Jon Skeet

1
@ JonSkeet คุณมีบทความหรือบล็อกที่ niggles เหล่านั้นคืออะไร? ฉันชอบที่จะไปถึงสิ่งที่คุณพบว่า "Niggles" :)
Nawfal

27

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


หากคลาส 'outer' ถือเป็นที่สิ้นสุดดังนั้นจึงไม่สามารถสร้างอินสแตนซ์ได้เลยอาร์กิวเมนต์นี้มีเหตุผลในกรณีนี้หรือไม่? เพราะมันมี / รักษาการอ้างอิงถึงชั้นนอกไม่มีประโยชน์ถ้าหลังเป็นที่สิ้นสุด
getsadzeg

10

คลาสภายในแบบไม่คงที่มีฟิลด์พิเศษที่ซ่อนอยู่ซึ่งชี้ไปที่อินสแตนซ์ของคลาสภายนอก ดังนั้นหากคลาส Entry ไม่คงที่นอกจากจะมีการเข้าถึงที่ไม่ต้องการแล้วจะมีพอยน์เตอร์สี่ตัวแทนที่จะเป็นสามตัว

ตามกฎแล้วฉันจะบอกว่าถ้าคุณกำหนดคลาสที่มีพื้นเพื่อทำหน้าที่เป็นชุดของข้อมูลสมาชิกเช่น "struct" ใน C ลองพิจารณาทำให้เป็นแบบสแตติก


8

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

class OuterClass {
    private OuterClass(int x) {
        System.out.println("x: " + x);
    }

    static class InnerClass {
        public static void test() {
            OuterClass outer = new OuterClass(1);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        OuterClass.InnerClass.test();
        // OuterClass outer = new OuterClass(1); // It is not possible to create outer instance from outside.
    }
}

นี่จะเอาต์พุต x: 1


7

คลาสที่ซ้อนกันแบบสแตติกเหมือนกับคลาสภายนอกอื่น ๆ เนื่องจากไม่สามารถเข้าถึงสมาชิกของคลาสภายนอกได้

เพื่อความสะดวกในการบรรจุหีบห่อเราสามารถนำคลาสที่ซ้อนกันแบบคงที่ออกเป็นคลาสนอกเพื่อการอ่านได้ นอกเหนือจากนี้ไม่มีกรณีการใช้งานอื่นของคลาสที่ซ้อนกันแบบคงที่

ตัวอย่างสำหรับการใช้งานประเภทนี้คุณสามารถหาได้ในไฟล์ Android R.java (แหล่งข้อมูล) โฟลเดอร์ Res ของ Android มีเลย์เอาต์ (มีการออกแบบหน้าจอ), โฟลเดอร์ drawable (มีภาพที่ใช้สำหรับโครงการ), โฟลเดอร์ค่า (ซึ่งมีค่าคงที่สตริง), ฯลฯ

เนื่องจากโฟลเดอร์ทั้งหมดเป็นส่วนหนึ่งของโฟลเดอร์ Res เครื่องมือ android สร้างไฟล์ R.java (แหล่งข้อมูล) ซึ่งภายในมีคลาสซ้อนกันแบบคงที่จำนวนมากสำหรับแต่ละโฟลเดอร์ภายในของพวกเขา

นี่คือรูปลักษณ์ของไฟล์ R.java ที่สร้างขึ้นใน android: ที่ นี่พวกเขาใช้เพื่อความสะดวกในการบรรจุเท่านั้น

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.techpalle.b17_testthird;

public final class R {
    public static final class drawable {
        public static final int ic_launcher=0x7f020000;
    }
    public static final class layout {
        public static final int activity_main=0x7f030000;
    }
    public static final class menu {
        public static final int main=0x7f070000;
    }
    public static final class string {
        public static final int action_settings=0x7f050001;
        public static final int app_name=0x7f050000;
        public static final int hello_world=0x7f050002;
    }
}

6

จากhttp://docs.oracle.com/javase/tutorial/java/javaOO/whentouse.html :

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


4

ตัวอย่างง่ายๆ:

package test;

public class UpperClass {
public static class StaticInnerClass {}

public class InnerClass {}

public static void main(String[] args) {
    // works
    StaticInnerClass stat = new StaticInnerClass();
    // doesn't compile
    InnerClass inner = new InnerClass();
}
}

ถ้าไม่คงที่คลาสไม่สามารถถูกทำให้เป็นอินสแตนซ์ในอินสแตนซ์ของชนชั้นสูง (ดังนั้นไม่ได้อยู่ในตัวอย่างที่ main เป็นฟังก์ชันคงที่)


StaticInnerClass ของคุณไม่ใช่ชั้นเรียนแบบซ้อน / ภายในแบบคงที่ มันเป็นระดับคงที่ระดับบนสุด
theRiley

2

หนึ่งในเหตุผลสำหรับการคงเทียบกับปกติต้องทำกับ classloading คุณไม่สามารถยกตัวอย่างชั้นเรียนภายในในตัวสร้างของมันเป็นผู้ปกครอง

PS: ฉันเข้าใจเสมอ 'ซ้อน' และ 'ภายใน' เพื่อใช้แทนกันได้ อาจมีความแตกต่างเล็กน้อยในเงื่อนไข แต่นักพัฒนา Java ส่วนใหญ่จะเข้าใจเช่นกัน


1

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


'static Inner' เป็นข้อขัดแย้งในแง่
มาร์ควิสแห่ง Lorne

1
@EJP, Sheesh ... ผู้คนต่างออกไปโดยชี้ให้เห็นเมื่อใดก็ตามที่มีคนพูดถึง "ชั้นในแบบคงที่" ...
Sakiboy

0

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

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


คลาสอินเนอร์ไม่ได้เป็นส่วนหนึ่งของอินสแตนซ์ของคลาสที่ปิดล้อม
มาร์ควิสแห่ง Lorne

คลาสภายในนั้นขึ้นอยู่กับคลาสที่ปิดล้อมและมีการเข้าถึงสมาชิกของคลาสที่ปิดดังนั้นจึงเป็นส่วนหนึ่งของคลาสที่ล้อมรอบ ในความเป็นจริงมันเป็นสมาชิก
theRiley

0

การใช้คลาสที่ซ้อนกันแบบคงที่มากกว่าแบบไม่คงที่อาจช่วยประหยัดช่องว่างในบางกรณี ตัวอย่างเช่นการใช้งานComparatorภายในชั้นเรียนพูดนักเรียน

public class Student {
  public static final Comparator<Student> BY_NAME = new ByName();
  private final String name;
  ...
  private static class ByName implements Comparator<Student> {
    public int compare() {...}
  }
}

จากนั้นstaticตรวจสอบให้แน่ใจว่าชั้นเรียนของนักเรียนมีผู้เปรียบเทียบเพียงคนเดียวแทนที่จะสร้างอินสแตนซ์ใหม่ทุกครั้งที่มีการสร้างอินสแตนซ์นักศึกษาใหม่


-1

Adavantage ของคลาสภายใน -

  1. ใช้ครั้งเดียว
  2. สนับสนุนและปรับปรุงการห่อหุ้ม
  3. readibility
  4. การเข้าถึงสนามส่วนตัว

หากไม่มีคลาสภายในของคลาสภายนอกจะไม่มีอยู่

class car{
    class wheel{

    }
}

คลาสภายในมีสี่ประเภท

  1. ชั้นในปกติ
  2. วิธีการชั้นในท้องถิ่น
  3. ชั้นในที่ไม่ระบุชื่อ
  4. ชั้นในคงที่

จุด ---

  1. จากระดับชั้นคงที่เราสามารถเข้าถึงสมาชิกแบบคงที่ของชั้นนอก
  2. ภายในชั้นในเราไม่สามารถประกาศสมาชิกคงที่
  3. inorder เพื่อเรียกใช้คลาสภายในปกติในพื้นที่คงที่ของคลาสภายนอก

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  4. inorder เพื่อเรียกใช้คลาสภายในปกติในพื้นที่อินสแตนซ์ของคลาสภายนอก

    Inner i=new Inner();

  5. inorder เพื่อเรียกใช้คลาสภายในปกตินอกคลาสภายนอก

    Outer 0=new Outer(); Outer.Inner i= O.new Inner();

  6. ด้านในชั้นในตัวชี้ไปยังชั้นในนี้

    this.member-current inner class outerclassname.this--outer class

  7. สำหรับตัวดัดแปลงคลาสที่ใช้งานได้ - สาธารณะ, ค่าเริ่มต้น,

    final,abstract,strictfp,+private,protected,static

  8. outer $ inner เป็นชื่อของชื่อคลาสภายใน

  9. ชั้นในภายในวิธีการอินสแตนซ์จากนั้นเราสามารถ acess คงที่และเขตข้อมูลตัวอย่างของชั้นนอก

10. ชั้นในภายในวิธีการแบบคงที่จากนั้นเราสามารถเข้าถึงเฉพาะสนามแบบคงที่ของ

ชั้นนอก

class outer{

    int x=10;
    static int y-20;

    public void m1() {
        int i=30;
        final j=40;

        class inner{

            public void m2() {
                // have accees x,y and j
            }
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.