เหตุใด Java จึงห้ามไม่ให้มีฟิลด์แบบคงที่ในคลาสภายใน


85
class OuterClass {
 class InnerClass {
  static int i = 100; // compile error
  static void f() { } // compile error
 }
} 

แม้ว่าจะไม่สามารถเข้าถึงฟิลด์แบบคงที่ได้OuterClass.InnerClass.iแต่ถ้าฉันต้องการบันทึกสิ่งที่ควรเป็นแบบคงที่เช่นจำนวนของออบเจ็กต์ InnerClass ที่สร้างขึ้นการทำให้ฟิลด์นั้นคงที่จะเป็นประโยชน์ ดังนั้นทำไมไม่ห้าม Java เขตข้อมูลคงที่ / วิธีการในการเรียนภายใน?

แก้ไข: ฉันรู้วิธีทำให้คอมไพเลอร์พอใจกับคลาสซ้อนแบบคงที่ (หรือคลาสภายในแบบคงที่) แต่สิ่งที่ฉันอยากรู้คือทำไม java จึงห้ามฟิลด์ / วิธีการแบบคงที่ภายในคลาสภายใน (หรือคลาสภายในธรรมดา) จากทั้งการออกแบบภาษาและ ด้านการนำไปใช้งานหากมีใครรู้เพิ่มเติมเกี่ยวกับเรื่องนี้


3
ตัวอย่างที่ฉันชอบคือมี Logger สำหรับชั้นในเท่านั้น มันไม่สามารถคงที่เหมือน Loggers อื่น ๆ ทั้งหมด
Piotr Findeisen

ตั้งแต่ Java 16 เนื่องจากไม่เป็นเช่นนั้นอีกต่อไป - ดูคำตอบนี้
Nicolai Parlog

คำตอบ:


32

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

8.1.2 ชั้นเรียนภายในและอินสแตนซ์การปิดล้อม

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


19
อาจจะเพิ่งตัดสินใจแบบนั้น
Gregory Pakosz

3
คุณไม่สามารถสร้างอินสแตนซ์ภายในที่ไม่คงที่โดยไม่มีการอ้างอิงระดับบนสุด แต่คุณยังสามารถเริ่มต้นได้
skaffman

ถ้า ClassLoaders เก็บแคชที่ระบุว่า "Class X ได้รับการเตรียมใช้งานแล้ว" จะไม่สามารถใช้ตรรกะของพวกเขาในการเริ่มต้นอินสแตนซ์ของ Class [อ็อบเจ็กต์ที่เป็นตัวแทน] X หลายอินสแตนซ์ (ซึ่งเป็นสิ่งที่จำเป็นเมื่อต้องสร้างอินสแตนซ์วัตถุคลาสเป็นคลาสภายในภายในหลาย ๆ วัตถุที่แตกต่างกัน)
Erwin Smout

@skaffman มันยังไม่สมเหตุสมผล คุณสมบัติคงที่ของคลาสภายในจะเริ่มต้นเพียงครั้งเดียวปัญหาจะเกิดอะไรขึ้น? ตอนนี้ฉันมีแฮชแมปแบบคงที่และฉันมีประมาณ 4 วิธีที่จัดการเฉพาะแผนที่นี้ทำให้การจัดกลุ่มทุกอย่างในคลาสชั้นในเป็นเรื่องที่ดียิ่งขึ้น อย่างไรก็ตามแฮชแมปแบบคงที่ตอนนี้จะต้องอาศัยอยู่ข้างนอกและอาจมีสิ่งอื่น ๆ ที่เกี่ยวข้องซึ่งเป็นเพียงเรื่องโง่ ๆ ธรรมดา อะไรคือปัญหาในการเริ่มต้นคุณสมบัติคงที่?
mmm

ตั้งแต่ Java 16 เนื่องจากไม่เป็นเช่นนั้นอีกต่อไป - ดูคำตอบนี้
Nicolai Parlog

56

สิ่งที่ฉันอยากรู้คือเหตุใด java จึงห้ามใช้ static fields / method ในคลาสภายใน

เนื่องจากคลาสภายในเหล่านั้นเป็นคลาสภายใน "อินสแตนซ์" นั่นคือเป็นเหมือนแอตทริบิวต์อินสแตนซ์ของวัตถุที่ปิดล้อม

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

เหมือนกับว่าคุณพยายามสร้างแอตทริบิวต์แบบคงที่ / อินสแตนซ์ในเวลาเดียวกัน

ใช้ตัวอย่างต่อไปนี้:

class Employee {
    public String name;
}

หากคุณสร้างพนักงานสองอินสแตนซ์:

Employee a = new Employee(); 
a.name = "Oscar";

Employee b = new Employee();
b.name = "jcyang";

เป็นที่ชัดเจนว่าทำไมแต่ละคนจึงมีมูลค่าของทรัพย์สินnameใช่ไหม?

สิ่งเดียวกันนี้เกิดขึ้นกับชั้นใน อินสแตนซ์คลาสภายในแต่ละอินสแตนซ์ไม่ขึ้นกับอินสแตนซ์คลาสภายในอื่น ๆ

ดังนั้นหากคุณพยายามสร้างcounterแอตทริบิวต์คลาสไม่มีวิธีใดที่จะแชร์ค่านั้นกับอินสแตนซ์สองอินสแตนซ์ที่แตกต่างกัน

class Employee {
    public String name;
    class InnerData {
        static count; // ??? count of which ? a or b? 
     }
}

เมื่อคุณสร้างอินสแตนซ์aและbในตัวอย่างด้านบนค่าที่ถูกต้องสำหรับตัวแปรคงcountคืออะไร ไม่สามารถระบุได้เนื่องจากการมีอยู่ของInnerDataคลาสขึ้นอยู่กับวัตถุที่ปิดล้อมแต่ละชิ้น

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

ฉันคิดว่าสิ่งนี้ฟังดูย้ำ แต่ถ้าคุณคิดถึงความแตกต่างระหว่างอินสแตนซ์กับคุณสมบัติคลาสมันจะสมเหตุสมผล


4
ฉันจะซื้อคำอธิบายของคุณสำหรับคุณสมบัติคงที่ของชั้นใน แต่ในขณะที่ @skaffman ชี้ให้เห็นในความคิดเห็นสำหรับคำตอบของฉันแล้ววิธีการคงที่ล่ะ? ดูเหมือนว่าควรอนุญาตวิธีการโดยไม่บังคับว่าจะแยกออกจากอินสแตนซ์ใด ๆ อันที่จริงใน java คุณสามารถเรียกวิธีการแบบคงที่ในอินสแตนซ์ได้ (แม้ว่าจะถือว่าเป็นสไตล์ที่ไม่ดีก็ตาม) BTW: ฉันขอให้เพื่อนร่วมงานพยายามรวบรวมรหัสของ OP เป็น C # และจะคอมไพล์ เห็นได้ชัดว่า C # อนุญาตสิ่งนี้ซึ่งแสดงให้เห็นว่าสิ่งที่ OP ต้องการทำไม่ได้ละเมิดหลักการ OO พื้นฐานบางประการ
Asaph

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

1
เกี่ยวกับ C # ... มันไม่ถูกต้อง OO เพียงเพราะ C # อนุญาตฉันไม่ได้หมายความว่ามันผิด แต่ C # มีกระบวนทัศน์หลายอย่างเพื่อให้การพัฒนาง่ายขึ้นแม้จะต้องเสียค่าใช้จ่ายที่สอดคล้องกัน (คุณต้องเรียนรู้สิ่งใหม่ ๆ กับ. NET แต่ละรุ่น) และมัน อนุญาตสิ่งนี้และสิ่งอื่น ๆ ในหมู่คนอื่น ๆ ฉันคิดว่านั่นเป็นสิ่งที่ดี หากชุมชนรู้สึกว่าฟีเจอร์พิเศษเจ๋งพอ C # อาจมีในอนาคต
OscarRyz

2
@OscarRyz ทำไมคุณต้องมีอินสแตนซ์ของคลาสภายในเพื่อใช้วิธีการ / ฟิลด์แบบคงที่ และตัวอย่างของวิธีการแบบคงที่ [มีประโยชน์] ในคลาสภายในคือเมธอดส่วนตัว
Leonid Semyonov

1
ด้วยการใช้finalฟิลด์แบบคงที่จะได้รับอนุญาตในคลาสภายในใน java คุณอธิบายสถานการณ์นี้อย่างไร
Number945

34

InnerClassไม่สามารถมีstaticสมาชิกได้เนื่องจากเป็นของอินสแตนซ์ (จากOuterClass) หากคุณประกาศInnerClassว่าstaticจะแยกออกจากอินสแตนซ์โค้ดของคุณจะคอมไพล์

class OuterClass {
    static class InnerClass {
        static int i = 100; // no compile error
        static void f() { } // no compile error
    }
}

BTW: คุณยังคงสามารถสร้างอินสแตนซ์ของInnerClassไฟล์. staticในบริบทนี้ช่วยให้สิ่งนี้เกิดขึ้นได้โดยไม่ต้องปิดอินสแตนซ์ของOuterClass.


6
InnerClassไม่ได้เป็นOuterClass, อินสแตนซ์ของมันทำ ทั้งสองคลาสเองไม่มีความสัมพันธ์ดังกล่าว คำถามที่ว่าทำไมคุณไม่สามารถมีวิธีการแบบInnerClassคงที่ได้
skaffman

10

จริงๆแล้วคุณสามารถประกาศเขตข้อมูลแบบคงที่ได้หากเป็นค่าคงที่และเขียนด้วยเวลาคอมไพล์

class OuterClass {
    void foo() {
        class Inner{
            static final int a = 5; // fine
            static final String s = "hello"; // fine
            static final Object o = new Object(); // compile error, because cannot be written during compilation
        }
    }
}

8
  1. ลำดับการเริ่มต้นคลาส เป็นเหตุผลสำคัญ

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

  • ใช้ฟิลด์สแตติกที่ประกาศโดย T และฟิลด์ไม่ใช่ตัวแปรคงที่

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

  1. มันจะละเมิดกฎพื้นฐานบางอย่าง คุณสามารถข้ามไปยังส่วนสุดท้าย (ถึงtwo cases) เพื่อหลีกเลี่ยงสิ่งที่ไม่มีออบ

สิ่งหนึ่งเกี่ยวกับเมื่อบางคนมีพฤติกรรมเหมือนกับคลาสปกติในทุกๆด้านและมีความเกี่ยวข้องกับคลาสชั้นนอกstatic nested classnested classstatic

แต่แนวคิดของInner class/ มันจะเชื่อมโยงกับคลาสด้านนอก / ปิดล้อมหรือไม่ โปรดทราบว่าเกี่ยวข้องกับอินสแตนซ์ไม่ใช่คลาส ตอนนี้การเชื่อมโยงกับอินสแตนซ์อย่างชัดเจนหมายความว่า ( จากแนวคิดของตัวแปรอินสแตนซ์ ) จะมีอยู่ภายในอินสแตนซ์และจะแตกต่างกันไปตามอินสแตนซ์ non-static nested classinstance

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

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

  • หากแชร์กับอินสแตนซ์ของคลาสภายในทั้งหมดจะเป็นการละเมิดแนวคิดของcontext of instance(ตัวแปรอินสแตนซ์) มันไม่ใช่แล้ว
  • หากไม่ได้แชร์กับอินสแตนซ์ทั้งหมดจะเป็นการละเมิดแนวคิดของการคงที่ อีกครั้ง NO.

5

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

ดังนั้นสมมติว่าคุณต้องการนับอินสแตนซ์คลาสภายในทั้งหมดคุณจะทำ:

public class Outer{
    int nofInner; //this will count the inner class 
                  //instances of this (Outer)object
                  //(you know, they "belong" to an object)
    static int totalNofInner; //this will count all 
                              //inner class instances of all Outer objects
    class Inner {
        public Inner(){
            nofInner++;
            totalNofInner++;
        }
    }
}

2
แต่คำถามคืออะไรคือสาเหตุที่อนุญาตให้มีการประกาศเขตข้อมูลคงที่เมื่อมีการประกาศfinalแล้ว?
ปลอบใจ

หากคุณดูที่ [ stackoverflow.com/a/1954119/1532220] (คำตอบของ OscarRyzs ด้านบน): แรงจูงใจของเขาคือค่าไม่สามารถเชื่อมโยงกับตัวแปรได้ แน่นอนว่าหากตัวแปรเป็นขั้นสุดท้ายคุณจะสามารถรู้ได้อย่างง่ายดายว่าจะกำหนดค่าอะไร (คุณต้องรู้)
ianos

2

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

หมายเหตุ: ปฏิบัติต่อคลาสภายในเสมอเหมือนตัวแปรสำหรับคลาสภายนอกซึ่งอาจเป็นแบบคงที่หรือไม่คงที่เหมือนตัวแปรอื่น ๆ


แต่ชั้นในสามารถมีstatic finalค่าคงที่
ฝนตก


1

เพราะจะทำให้เกิดความคลุมเครือในความหมายของ "สถิต".

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

นำมาจาก "Core Java SE 9 for the Impatient" โดย Cay S. Horstmann หน้า 90 บทที่ 2.6.3


0

ตั้งแต่ Java 16 เป็นต้นไปจะไม่เป็นเช่นนั้นอีกต่อไป อ้างอิงจากJEP 395 (ในการสรุปบันทึก):

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

อันที่จริงโค้ดต่อไปนี้สามารถคอมไพล์ด้วย Java 16 (ลองด้วย 16.ea.27):

public class NestingClasses {

    public class NestedClass {

        static final String CONSTANT = new String(
                "DOES NOT COMPILE WITH JAVA <16");

        static String constant() {
            return CONSTANT;
        }

    }

}

-1

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


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