ทำไม super.super.method (); ไม่อนุญาตให้ใช้ใน Java?


360

ฉันอ่านคำถามนี้และคิดว่าจะแก้ไขได้อย่างง่ายดาย (ไม่ใช่ว่ามันจะไม่สามารถแก้ไขได้โดยไม่ต้องถ้า) สามารถเขียน:

@Override
public String toString() {
    return super.super.toString();
}

ฉันไม่แน่ใจว่ามันมีประโยชน์ในหลาย ๆ กรณี แต่ฉันสงสัยว่าทำไมมันถึงไม่เป็นเช่นนั้นและหากมีสิ่งนี้ในภาษาอื่น

พวกคุณคิดอย่างไร

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


7
ต้องการเรียกร้องsuper.super.toString()การตัดสินใจของคุณเองเมื่อคุณเลือกที่จะขยายชั้นเรียนเพื่อยอมรับคุณสมบัติทั้งหมดของมัน (ไม่ใช่บางส่วน)
DayaMoon

คำตอบ:


484

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

public class Items
{
    public void add(Item item) { ... }
}

public class RedItems extends Items
{
    @Override
    public void add(Item item)
    {
        if (!item.isRed())
        {
            throw new NotRedItemException();
        }
        super.add(item);
    }
}

public class BigRedItems extends RedItems
{
    @Override
    public void add(Item item)
    {
        if (!item.isBig())
        {
            throw new NotBigItemException();
        }
        super.add(item);
    }
}

ไม่เป็นไร RedItems มั่นใจได้เสมอว่ารายการที่บรรจุอยู่นั้นเป็นสีแดงทั้งหมด ตอนนี้สมมติว่าเรามีความสามารถที่จะเรียก super.super.add ():

public class NaughtyItems extends RedItems
{
    @Override
    public void add(Item item)
    {
        // I don't care if it's red or not. Take that, RedItems!
        super.super.add(item);
    }
}

ตอนนี้เราสามารถเพิ่มสิ่งที่เราชอบและค่าคงที่ในRedItemsจะแตก

มันสมเหตุสมผลไหม


38
ตัวอย่างที่ดี แต่ฉันคิดว่ามันเป็นการออกแบบที่ไม่ดีเมื่อคลาสฐานยอมรับไอเท็ม แต่คลาสที่ได้รับนั้นปฏิเสธเพราะคลาสที่ได้รับนั้นไม่สามารถใช้เป็นแบบดรอปดาวน์สำหรับคลาสพื้นฐานได้ (ละเมิดหลักการทดแทน) คิดถูกหรือว่าลำดับชั้นนั้นสะอาด
Johannes Schaub - litb

5
คุณหมายถึงการแต่งเพลงมากกว่ามรดกหรือไม่ ตัวอย่างนี้ไม่ใช่เหตุผลที่จะหลีกเลี่ยงการสืบทอด - แม้ว่าจะมีคนอื่นมากมาย
Jon Skeet

3
@Tim: นี่เป็นเพียงตัวอย่างที่เข้าใจง่ายของการละเมิด encapsulation อาจเป็นการตั้งค่าคุณสมบัติแทน นอกจากนี้จะไม่สามารถมองเห็นได้ทุกด้านในระดับพิมพ์ ยาสามัญไม่ใช่คำตอบสำหรับทุกสิ่ง
Jon Skeet

12
@ JohannesSchaub-litb ฉันคิดว่ามันเป็นการละเมิดหลักการทดแทน Liskov เนื่องจากหากรหัสหนึ่งสำหรับสัญญาของ Item และใช้อินสแตนซ์ของ RedItems เราจะได้รับ NotRedItemException ที่ไม่คาดคิด ฉันได้รับการสอนเสมอว่าคลาสย่อยควรรับชุดข้อมูลเข้าสุด ๆ และส่งคืนชุดย่อยของผลลัพธ์ กล่าวอีกนัยหนึ่งคลาสย่อยไม่ควรปฏิเสธอินพุตที่ถูกต้องสำหรับซูเปอร์คลาสหรือสร้างเอาต์พุตที่ซูเปอร์คลาสไม่สามารถสร้างได้ซึ่งรวมถึงการโยนข้อผิดพลาดที่คลาสซูเปอร์ไม่ได้โยน
Konstantin Tarashchanskiy

4
@piechuckerr: บางครั้งหนังสือบางครั้งบล็อกโพสต์บางครั้งก็มีประสบการณ์ ...
จอนสกีต

72

ฉันคิดว่า Jon Skeet มีคำตอบที่ถูกต้อง ฉันแค่ต้องการเพิ่มที่คุณสามารถเข้าถึงตัวแปรเงาจากซูเปอร์คลาสของซูเปอร์คลาสโดยการคัดเลือกthis:

interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
        int x = 3;
        void test() {
                System.out.println("x=\t\t"          + x);
                System.out.println("super.x=\t\t"    + super.x);
                System.out.println("((T2)this).x=\t" + ((T2)this).x);
                System.out.println("((T1)this).x=\t" + ((T1)this).x);
                System.out.println("((I)this).x=\t"  + ((I)this).x);
        }
}

class Test {
        public static void main(String[] args) {
                new T3().test();
        }
}

ซึ่งผลิตผลลัพธ์:

x = 3
super.x = 2
((T2) สิ่งนี้) .x = 2
((T1) สิ่งนี้) .x = 1
((I) สิ่งนี้) .x = 0

(ตัวอย่างจากJLS )

อย่างไรก็ตามวิธีนี้ใช้ไม่ได้กับการเรียกใช้เมธอดเนื่องจากการเรียกใช้เมธอดจะถูกกำหนดตามชนิดรันไทม์ของวัตถุ


6
หากคุณมีตัวแปรชื่อเดียวกับใน superclass และด้วยเหตุผลบางอย่างที่คุณไม่สามารถเปลี่ยน (หรือไม่ได้รับอนุญาต) คุณสามารถเข้าถึงตัวแปรของ superclass ด้วยชื่อเดียวกันได้ คุณใช้อะไร ดี ... ฉันไม่เคยใช้มัน
Michael Myers

ลิงก์ที่ยอดเยี่ยมไปยังเอกสารนิพจน์ ตัวอย่างที่ดีบางอย่างสำหรับคนที่เรียน OCPJP
Gordon

น่ากลัว ฉันไม่เคยคิดที่จะใช้นักแสดง
Thomas Eding

คลาส T1 มาแทนที่ตัวแปร x ได้อย่างไร? มันคงสุดท้ายโดยค่าเริ่มต้นใช่มั้ย
amarnath harish

@amarnathharish: มันไม่ได้ถูกแทนที่และเขตข้อมูลจะได้รับการคุ้มครองเป็นค่าเริ่มต้น
Michael Myers

41

ฉันคิดว่ารหัสต่อไปนี้อนุญาตให้ใช้ super.super ... super.method () ในกรณีส่วนใหญ่ (แม้ว่าจะทำอย่างนั้นก็ตาม)

ในระยะสั้น

  1. สร้างอินสแตนซ์ชั่วคราวของประเภทบรรพบุรุษ
  2. คัดลอกค่าของฟิลด์จากวัตถุต้นฉบับไปยังฟิลด์ชั่วคราว
  3. เรียกใช้วิธีการเป้าหมายบนวัตถุชั่วคราว
  4. คัดลอกค่าที่ปรับเปลี่ยนกลับไปเป็นวัตถุดั้งเดิม

การใช้งาน:

public class A {
   public void doThat() { ... }
}

public class B extends A {
   public void doThat() { /* don't call super.doThat() */ }
}

public class C extends B {
   public void doThat() {
      Magic.exec(A.class, this, "doThat");
   }
}


public class Magic {
    public static <Type, ChieldType extends Type> void exec(Class<Type> oneSuperType, ChieldType instance,
            String methodOfParentToExec) {
        try {
            Type type = oneSuperType.newInstance();
            shareVars(oneSuperType, instance, type);
            oneSuperType.getMethod(methodOfParentToExec).invoke(type);
            shareVars(oneSuperType, type, instance);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private static <Type, SourceType extends Type, TargetType extends Type> void shareVars(Class<Type> clazz,
            SourceType source, TargetType target) throws IllegalArgumentException, IllegalAccessException {
        Class<?> loop = clazz;
        do {
            for (Field f : loop.getDeclaredFields()) {
                if (!f.isAccessible()) {
                    f.setAccessible(true);
                }
                f.set(target, f.get(source));
            }
            loop = loop.getSuperclass();
        } while (loop != Object.class);
    }
}

10
ด้วยการสะท้อนคุณสามารถทำอะไรได้ใช่ :) คุณสามารถสร้างสตริงที่ไม่แน่นอนได้
BalusC

23
ช่างเป็นสิ่งที่น่ากลัวมาก! ฉันให้คุณ +1 เพียงเพื่อหาวิธีที่จะทำมันได้ที่ :) ทั้งหมด
ลาร์รีวาตานาเบะ

6
มันเป็นกลอุบายที่ดี แต่ถึงแม้จะไม่เสมอไปกับการเรียก super.super ที่ไม่สามารถใช้งานได้ แต่จำเป็นต้องใช้และนั่นเป็นเพราะการเรียก super.super จะมีบริบทของ C (C + B + A) ในขณะที่คำตอบของคุณสร้างอินสแตนซ์ ของ A ที่ไม่มีบริบทของ B และ C ดังนั้นคำตอบนี้จะไม่ทำงานถ้าทุก doThat ที่เรียกว่า getContext () และ getContext ถูกนำไปใช้ในแต่ละชั้นเรียนแตกต่างกัน ในคำตอบของคุณมันจะใช้ getContext () ของ A ในขณะที่เรียกใช้ super.super ที่ไม่สามารถใช้งานได้จะส่งผลให้ใช้ getContext ของ C
inor

อืมมม ในบางกรณีคุณอาจเอาชนะการคัดค้านของinorด้วยพร็อกซีแบบไดนามิก ( javahowto.blogspot.co.uk/2011/12/… ) วิธีการเปลี่ยนเส้นทางการโทรไปยังวัตถุเดิม (การซิงโครไนซ์ตัวแปรหลังจากการโทรแต่ละครั้ง)? ดูเหมือนว่าพร็อกซีต้องการทุกสิ่งที่จะใช้งานอินเทอร์เฟซ นอกจากนี้ผมสงสัยว่ามันเป็นไปได้สำหรับชั้นซุปเปอร์ซุปเปอร์ที่จะเรียกวิธีใดวิธีหนึ่งซุปเปอร์ซุปเปอร์เฉพาะและคุณต้องการไม่จำเป็นต้องเปลี่ยนเส้นทางเหล่านั้น ....
Erhannis

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

11

ฉันไม่มีชื่อเสียงพอที่จะแสดงความคิดเห็นดังนั้นฉันจะเพิ่มสิ่งนี้ในคำตอบอื่น ๆ

Jon Skeet ตอบอย่างยอดเยี่ยมพร้อมตัวอย่างที่สวยงาม Matt B มีประเด็น: superclasses ทุกคนไม่มีสิทธิ์เหนือกว่า รหัสของคุณจะแตกถ้าคุณเรียกว่าสุดยอดสุด ๆ ซึ่งไม่มีอะไรเป็นพิเศษ

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

โดยส่วนตัวแล้วฉันพบว่าข้อ จำกัด นี้เป็นหนึ่งในจุดแข็งที่ยิ่งใหญ่ที่สุดของ Java รหัสค่อนข้างเข้มงวดเมื่อเทียบกับภาษาอื่นที่ฉันเคยใช้ แต่ฉันรู้เสมอว่าจะคาดหวังอะไร สิ่งนี้จะช่วยให้มีเป้าหมาย "ง่ายและคุ้นเคย" ของ Java ในใจของฉันการเรียก super.super นั้นไม่ง่ายหรือไม่คุ้นเคย บางทีนักพัฒนาอาจรู้สึกเหมือนกัน?


3
คุณพูดว่า "ไม่ใช่ซูเปอร์คลาสทั้งหมดที่มีอยู่เหนือ" ดีทั้งหมดยกเว้น java.lang.Object สิ่งที่อาจให้ "null" แก่คุณ ดังนั้นฉันจะบอกว่าเกือบทั้งหมดมี supers
ทิมBüthe

ระดับโปรแกรมเมอร์แอพลิเคชันเขียนทุกคนมี "ซูเปอร์" (java.lang.Object ไม่ได้ แต่โปรแกรมเมอร์ใบสมัครไม่ได้เขียนว่า.)
finnw

2
แก้ไขได้อย่างง่ายดายโดยการทำ super.super.super ... ข้อผิดพลาดในการรวบรวมเวลาสุด ๆ ถ้ามีจำนวนมากเกินไป พิจารณา Java มีการสืบทอดสาธารณะเท่านั้นหากมีคนเปลี่ยนลำดับชั้นการสืบทอดคุณกำลังเปลี่ยนอินเตอร์เฟส ดังนั้นฉันไม่ต้องกังวลว่าซูเปอร์ ^ n จะน่ากลัว
Thomas Eding

7

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

ตามที่แสดงโดยผู้โพสต์คนอื่น ๆ มันเป็นไปได้ที่จะทำสิ่งนี้ผ่านการสะท้อนกลับ แต่มันควรจะเป็นไปได้ที่จะทำอะไรแบบนี้

(SuperSuperClass นี้). TheMethod ();

ฉันกำลังจัดการกับปัญหานี้ในขณะนี้ - การแก้ไขอย่างรวดเร็วคือการคัดลอกและวางวิธี superclass ลงในวิธีการ sububclass :)


1
การส่งเมธอดก่อนการเรียกใช้เมธอดไม่ได้เปลี่ยนวิธีการที่ได้รับมอบหมายซึ่งเป็นการนำไปใช้ของคลาสย่อยที่ใช้เสมอ ดูเช่นstackoverflow.com/questions/1677993/…
Joshua Goldberg

1
@ Larry นี่เป็นสถานการณ์ที่ฉันอยู่และการแก้ไขที่ฉันใช้ โทรดี!
bcr

หากคุณมีรหัสวิธีการที่จำเป็นคุณสามารถเปิดเขตข้อมูลที่จำเป็นทั้งหมดและเรียกใช้รหัสนี้ในคลาสย่อยของคุณ
Enyby

6

นอกจากประเด็นที่ดีมาก ๆ ที่คนอื่นทำผมคิดว่ามีเหตุผลอื่น: ถ้าซูเปอร์คลาสไม่มีซูเปอร์คลาสล่ะ?

เนื่องจากทุกคลาสขยายอย่างเป็นธรรมชาติ (อย่างน้อย) Objectจึงsuper.whatever()มักอ้างถึงวิธีการในซูเปอร์คลาส แต่จะเกิดอะไรขึ้นถ้าชั้นเรียนของคุณขยายออกไปObject- แล้วจะsuper.superอ้างอิงอะไรล่ะ? ควรจัดการพฤติกรรมนั้นอย่างไร - ข้อผิดพลาดของคอมไพเลอร์ NullPointer ฯลฯ

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


4
เห็นได้ชัดว่ามันเป็นข้อผิดพลาดของคอมไพเลอร์ - และเป็นข้อมูลที่คอมไพเลอร์มี
Michael Borgwardt

4

ฉันคิดว่าถ้าคุณเขียนทับวิธีและต้องการให้ทุกเวอร์ชั่นชั้นสูงของมัน (เช่นพูดเพื่อequals) แล้วคุณมักจะต้องการเรียกโดยตรงรุ่น superclass โดยตรงก่อนซึ่งหนึ่งจะเรียกรุ่น superclass ในทางกลับกันถ้ามันต้องการ .

ฉันคิดว่ามันไม่ค่อยสมเหตุสมผลเท่าไร (ถ้าเป็นอย่างนั้นฉันไม่สามารถนึกถึงกรณีที่เกิดขึ้นได้) เพื่อเรียกใช้เมธอดเวอร์ชันคลาสซูเปอร์คลาสโดยพลการ ฉันไม่รู้ว่าเป็นไปได้ทั้งหมดใน Java สามารถทำได้ใน C ++:

this->ReallyTheBase::foo();

6
มันสมเหตุสมผลถ้ามีคนเขียน subclass ด้วยวิธีการหนึ่งที่นำไปใช้อย่างไม่ถูกต้อง แต่วิธี superclass ทำ 90% ของงาน จากนั้นคุณต้องการสร้างคลาสย่อยและแทนที่เมธอดที่เรียกใช้เมธอด superclass superclass และเพิ่ม 10% ของคุณเอง
ลาร์รีวาตานาเบะ

3

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

ซึ่งดูเหมือนว่าฉันจะขัดกับหลักการ OO เนื่องจากผู้ปกครองโดยตรงของชั้นเรียนควรมีความสัมพันธ์ใกล้ชิดกับชั้นเรียนของคุณมากกว่าปู่ย่าตายาย


3

ดูนี้โครงการ Github โดยเฉพาะอย่างยิ่งตัวแปร objectHandle โครงการนี้แสดงวิธีเรียกวิธีปู่ย่าตายายที่แท้จริงและถูกต้องในหลาน

ในกรณีที่ลิงก์เสียนี่คือรหัส:

import lombok.val;
import org.junit.Assert;
import org.junit.Test;

import java.lang.invoke.*;

/*
Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
Please don't actually do this... :P
*/
public class ImplLookupTest {
    private MethodHandles.Lookup getImplLookup() throws NoSuchFieldException, IllegalAccessException {
        val field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
        field.setAccessible(true);
        return (MethodHandles.Lookup) field.get(null);
    }

    @Test
    public void test() throws Throwable {
        val lookup = getImplLookup();
        val baseHandle = lookup.findSpecial(Base.class, "toString",
            MethodType.methodType(String.class),
            Sub.class);
        val objectHandle = lookup.findSpecial(Object.class, "toString",
            MethodType.methodType(String.class),
            // Must use Base.class here for this reference to call Object's toString
            Base.class);
        val sub = new Sub();
        Assert.assertEquals("Sub", sub.toString());
        Assert.assertEquals("Base", baseHandle.invoke(sub));
        Assert.assertEquals(toString(sub), objectHandle.invoke(sub));
    }

    private static String toString(Object o) {
        return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode());
    }

    public class Sub extends Base {
        @Override
        public String toString() {
            return "Sub";
        }
    }

    public class Base {
        @Override
        public String toString() {
            return "Base";
        }
    }
}

Happy Coding !!!!


นักวิทยาศาสตร์ของคุณหมกมุ่นอยู่กับสิ่งที่ทำได้หรือไม่พวกเขาไม่หยุดคิดว่าควรจะทำ โปรดอย่าทำเช่นนี้ ... : P 🤔
Tim Büthe

ใช่คำเหล่านี้เป็นคำพูดของนักเขียนไม่ใช่ของฉัน ในความคิดของฉันฉันคิดว่าคุณอาจต้องการมันจริง ๆ สักวันหนึ่ง
kyay

2

ฉันจะใส่เนื้อหาวิธี super.super ในวิธีอื่นถ้าเป็นไปได้

class SuperSuperClass {
    public String toString() {
        return DescribeMe();
    }

    protected String DescribeMe() {
        return "I am super super";
    }
}

class SuperClass extends SuperSuperClass {
    public String toString() {
        return "I am super";
    }
}

class ChildClass extends SuperClass {
    public String toString() {
        return DescribeMe();
    }
}

หรือถ้าคุณไม่สามารถเปลี่ยนคลาสซูเปอร์ซูเปอร์คุณสามารถลองนี้:

class SuperSuperClass {
    public String toString() {
        return "I am super super";
    }
}

class SuperClass extends SuperSuperClass {
    public String toString() {
        return DescribeMe(super.toString());
    }

    protected String DescribeMe(string fromSuper) {
        return "I am super";
    }
}

class ChildClass extends SuperClass {
    protected String DescribeMe(string fromSuper) {
        return fromSuper;
    }
}

ในทั้งสองกรณี

new ChildClass().toString();

ผลลัพธ์ไปที่ "I am super super"


ฉันเพิ่งพบว่าตัวเองอยู่ในสถานการณ์ที่ฉันเป็นเจ้าของ SuperSuperClass และ ChildClass แต่ไม่ใช่ SuperClass ดังนั้นฉันจึงพบโซลูชันแรกที่มีประโยชน์
xofon

1

ดูเหมือนว่าจะเป็นไปได้ที่อย่างน้อยก็จะได้คลาสของซูเปอร์คลาสของซูเปอร์คลาสแม้ว่าจะไม่จำเป็นต้องเป็นตัวอย่างของมันก็ตามโดยใช้การสะท้อนกลับ หากสิ่งนี้อาจมีประโยชน์โปรดพิจารณา Javadoc ที่http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass ()


1
public class A {

     @Override
     public String toString() {
          return "A";
     }

}


public class B extends A {

     @Override
     public String toString() {
          return "B";
     }

}

public class C extends B {

     @Override
     public String toString() {
          return "C";
     }

}


public class D extends C {

     @Override
     public String toString() {
          String result = "";
          try {
                result = this.getClass().getSuperclass().getSuperclass().getSuperclass().newInstance().toString();
          } catch (InstantiationException ex) {
                Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
          } catch (IllegalAccessException ex) {
                Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
          }
          return result;
     }

}

public class Main {

     public static void main(String... args) {
          D d = new D();
          System.out.println(d);

     }
}

run: A BUILD SUCCESSFUL (เวลาทั้งหมด: 0 วินาที)


1
ฉันเห็น แต่คุณกำลังสร้างอินสแตนซ์ใหม่ดังนั้นสิ่งนี้จะไม่ทำงานหากวัตถุมีสถานะใด ๆ
ทิมBüthe

1

การเรียก super.super.method () สมเหตุสมผลเมื่อคุณไม่สามารถเปลี่ยนรหัสของคลาสพื้นฐานได้ สิ่งนี้มักจะเกิดขึ้นเมื่อคุณขยายไลบรารีที่มีอยู่

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

package com.company.application;

public class OneYouWantExtend extends OneThatContainsDesiredMethod {

    // one way is to rewrite method() to call super.method() only or 
    // to doStuff() and then call super.method()

    public void method() {
        if (isDoStuff()) {
            // do stuff
        }
        super.method();
    }

    protected abstract boolean isDoStuff();


    // second way is to define methodDelegate() that will call hidden super.method()

    public void methodDelegate() {
        super.method();
    }
    ...
}

public class OneThatContainsDesiredMethod {

    public void method() {...}
    ...
}

ตัวอย่างเช่นคุณสามารถสร้างคลาส org.springframework.test.context.junit4.SpringJUnit4ClassRunnerในแอปพลิเคชันของคุณเพื่อให้คลาสนี้ควรโหลดก่อนของจริงจาก jar จากนั้นเขียนเมธอดหรือตัวสร้างใหม่

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


1

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

เราบรรลุสิ่งนี้โดยการแนะนำสมาชิกบูลีนใน CustomBaseClass ซึ่งสามารถใช้ในการเลื่อนการปรับใช้แบบกำหนดเองและเลือกให้กับการใช้งานเฟรมเวิร์กเริ่มต้นตามที่ต้องการ

        ...
        FrameworkBaseClass (....) extends...
        {
           methodA(...){...}
           methodB(...){...}
        ...
           methodX(...)
        ...
           methodN(...){...}

        }
        /* CustomBaseClass overrides default framework functionality for benefit of several derived classes.*/
        CustomBaseClass(...) extends FrameworkBaseClass 
        {
        private boolean skipMethodX=false; 
        /* implement accessors isSkipMethodX() and setSkipMethodX(boolean)*/

           methodA(...){...}
           methodB(...){...}
        ...
           methodN(...){...}

           methodX(...){
                  if (isSkipMethodX()) {
                       setSKipMethodX(false);
                       super.methodX(...);
                       return;
                       }
                   ... //common method logic
            }
        }

        DerivedClass1(...) extends CustomBaseClass
        DerivedClass2(...) extends CustomBaseClass 
        ...
        DerivedClassN(...) extends CustomBaseClass...

        DerivedClassX(...) extends CustomBaseClass...
        {
           methodX(...){
                  super.setSKipMethodX(true);
                  super.methodX(...);
                       }
        }

อย่างไรก็ตามด้วยหลักการทางสถาปัตยกรรมที่ดีตามมาในกรอบงานและแอพเราสามารถหลีกเลี่ยงสถานการณ์ดังกล่าวได้อย่างง่ายดายโดยใช้วิธี hasA แทนที่จะเป็นวิธี isA แต่ในบางครั้งมันก็ไม่เหมาะที่จะคาดหวังว่าจะมีสถาปัตยกรรมที่ออกแบบมาอย่างดีและดังนั้นจึงจำเป็นต้องหลีกเลี่ยงหลักการออกแบบที่แข็งแกร่งและแนะนำแฮ็กแบบนี้ แค่ 2 เซ็นต์ของฉัน ...


1

@Jon Skeet คำอธิบายที่ดี IMO ถ้ามีใครบางคนต้องการที่จะเรียกวิธีการ super.super แล้วหนึ่งจะต้องต้องการที่จะละเลยพฤติกรรมของผู้ปกครองทันที แต่ต้องการเข้าถึงพฤติกรรมผู้ปกครองที่ยิ่งใหญ่ สามารถทำได้ผ่านตัวอย่างของ ตามรหัสด้านล่าง

public class A {
    protected void printClass() {
        System.out.println("In A Class");
    }
}

public class B extends A {

    @Override
    protected void printClass() {
        if (!(this instanceof C)) {
            System.out.println("In B Class");
        }
        super.printClass();
    }
}

public class C extends B {
    @Override
    protected void printClass() {
        System.out.println("In C Class");
        super.printClass();
    }
}

นี่คือคลาสไดรเวอร์

public class Driver {
    public static void main(String[] args) {
        C c = new C();
        c.printClass();
    }
}

ผลลัพธ์ของสิ่งนี้จะเป็น

In C Class
In A Class

ลักษณะการทำงานคลาส B printClass จะถูกละเว้นในกรณีนี้ ฉันไม่แน่ใจว่านี่เป็นวิธีปฏิบัติที่ดีหรือดีเพื่อให้บรรลุถึง super.super แต่ก็ยังใช้งานได้อยู่


1
นั่นมันสร้างสรรค์ แต่ไม่ได้ตอบคำถามของฉัน C ยังคงไม่เรียก super.super แต่ B จะทำงานต่างกัน หากคุณสามารถเปลี่ยน A และ B คุณสามารถเพิ่มวิธีอื่นแทนการใช้ instanceof Super.super.foo จะช่วยคุณในกรณีที่คุณไม่สามารถเข้าถึง A และ B และไม่สามารถเปลี่ยนได้
ทิมBüthe

เห็นด้วย @TimButhe, แต่ถ้าใครอยากจะเรียก super.super แล้วเขา / เธอจงใจต้องการละเว้นพฤติกรรมของคลาส parent ดังนั้นคุณเพียงแค่ต้องบรรลุสิ่งนั้นด้วยไวยากรณ์ที่มีอยู่ของจาวา (ตัวเลือกใด ๆ ที่คุณต้องการเช่น instanceof / วิธีที่แตกต่าง)
Sanjay Jain

0

ถ้าคุณคิดว่าคุณต้องการซุปเปอร์คลาสคุณสามารถอ้างอิงมันในตัวแปรสำหรับคลาสนั้นได้ ตัวอย่างเช่น:

public class Foo
{
  public int getNumber()
  {
    return 0;
  }
}

public class SuperFoo extends Foo
{
  public static Foo superClass = new Foo();
  public int getNumber()
  {
    return 1;
  }
}

public class UltraFoo extends Foo
{
  public static void main(String[] args)
  {
    System.out.println(new UltraFoo.getNumber());
    System.out.println(new SuperFoo().getNumber());
    System.out.println(new SuperFoo().superClass.getNumber());
  }
  public int getNumber()
  {
    return 2;
  }
}

ควรพิมพ์ออกมา:

2
1
0

2
exmaple ของคุณนั้นค่อนข้างแย่มากเพราะคุณใช้วิธีการคงที่ เมื่อใช้วิธีการคงที่คุณไม่ต้องการตัวแปรหรือ super เลย บางทีคุณอาจพลาดแนวคิด OO พื้นฐานบางอย่างที่นี่โปรดอ่านให้ดี ต้องลงคะแนนขอโทษ
ทิมBüthe

1
มันสามารถทำได้อย่างง่ายดายโดยไม่มีวิธีการคงที่ มันง่ายพอ ฉันจะยกตัวอย่างง่ายๆเกี่ยวกับวิธีการทำเช่นนี้
Ashtheking

ฉันได้รับจุดของคุณการเก็บตัวแปร super ในฟิลด์เป็นวิธีหนึ่งในการแก้ปัญหานี้ แต่คุณไม่ต้องการตัวแปรถ้ามันเป็นวิธีการคงที่คุณก็สามารถใช้มันได้ ประการที่สองการเรียกใช้วิธีการคงที่หนึ่งตัวแปรคือการปฏิบัติที่ไม่ดีและ IDEs ส่วนใหญ่จะเตือนคุณเกี่ยวกับเรื่องนั้น หากคุณแก้ไขคำตอบของคุณลบสิ่งที่คงที่ฉันจะดีใจที่จะลบ downvote ของฉันไม่มีความผิด
ทิมBüthe

คุณปิด แต่รหัสของคุณจะไม่รวบรวม คุณพยายามเข้าถึง getNumber จากบริบทแบบคงที่ในวิธีการหลักของคุณ คุณพยายามรวบรวมสิ่งนี้จริง ๆ ? (และไม่ควร UltraFoo ของคุณขยาย SuperFoo?)
ทิมBüthe

ฉันไม่ต้องการที่จะโหดร้ายกับคุณ แต่new UltraFoo.getNumber()จะไม่รวบรวมเพราะคุณพลาดวงเล็บที่นั่น อย่างไรก็ตามฉันเพิ่งลบ Donvote ออกเนื่องจากแนวคิดของรหัสของคุณค่อนข้างชัดเจนแล้วขอบคุณ!
ทิมBüthe

0

IMO มันเป็นวิธีที่สะอาดในการบรรลุsuper.super.sayYourName()พฤติกรรมใน Java

public class GrandMa {  
    public void sayYourName(){  
        System.out.println("Grandma Fedora");  
    }  
}  

public class Mama extends GrandMa {  
    public void sayYourName(boolean lie){  
        if(lie){   
            super.sayYourName();  
        }else {  
            System.out.println("Mama Stephanida");  
        }  
    }  
}  

public class Daughter extends Mama {  
    public void sayYourName(boolean lie){  
        if(lie){   
            super.sayYourName(lie);  
        }else {  
            System.out.println("Little girl Masha");  
        }  
    }  
}  

public class TestDaughter {
    public static void main(String[] args){
        Daughter d = new Daughter();

        System.out.print("Request to lie: d.sayYourName(true) returns ");
        d.sayYourName(true);
        System.out.print("Request not to lie: d.sayYourName(false) returns ");
        d.sayYourName(false);
    }
}

เอาท์พุท:

Request to lie: d.sayYourName(true) returns Grandma Fedora
Request not to lie: d.sayYourName(false) returns Little girl Masha


อาคุณกำลังสนับสนุนการใช้ลำดับชั้นของชั้นเช่นนี้หรือไม่? แต่น่าเสียดายที่สิ่งนี้เริ่มยุ่งมากหากคุณต้องการเข้าถึงวิธีการใน Mama จาก Baby (คลาสย่อยของ Daughter) ...
bcr

yakov fain นั้นถูกต้อง มันไม่ได้เป็นตัวอย่างที่ดีเพราะคำถามดั้งเดิมเกี่ยวกับการเรียกวิธีการแทนที่ใน super.super
inor

0

ฉันคิดว่านี่เป็นปัญหาที่ทำลายข้อตกลงการสืบทอด
โดยการขยายชั้นเรียนที่คุณเชื่อฟัง / เห็นด้วยพฤติกรรมของคุณสมบัติ
ในขณะที่โทรsuper.super.method()คุณต้องการที่จะทำลายข้อตกลงการเชื่อฟังของคุณเอง

คุณไม่สามารถเลือกเชอร์รี่จากคลาส super ได้ได้

อย่างไรก็ตามอาจมีสถานการณ์ที่เกิดขึ้นเมื่อคุณรู้สึกว่าจำเป็นต้องโทรsuper.super.method()- มักจะเป็นสัญญาณการออกแบบที่ไม่ดีในรหัสของคุณหรือในรหัสที่คุณสืบทอด!
หากซูเปอร์และซุปเปอร์ซุปเปอร์เรียนไม่สามารถ refactored (รหัสเดิมบางส่วน) จากนั้นเลือกสำหรับองค์ประกอบมรดก

การแยก Encapsulation คือเมื่อคุณ@Overrideวิธีการบางอย่างด้วยการทำลายรหัสที่ห่อหุ้ม วิธีการที่ได้รับการออกแบบเพื่อไม่ให้มีการทำเครื่องหมายแทนที่ สุดท้าย


0

ใน C # คุณสามารถเรียกวิธีการของบรรพบุรุษใด ๆ เช่นนี้:

public class A
    internal virtual void foo()
...
public class B : A
    public new void foo()
...
public class C : B
    public new void foo() {
       (this as A).foo();
    }

นอกจากนี้คุณสามารถทำได้ใน Delphi:

type
   A=class
      procedure foo;
      ...
   B=class(A)
     procedure foo; override;
     ...
   C=class(B)
     procedure foo; override;
     ...
A(objC).foo();

แต่ใน Java คุณสามารถทำการโฟกัสได้ด้วยอุปกรณ์บางอย่างเท่านั้น วิธีหนึ่งที่เป็นไปได้คือ:

class A {               
   int y=10;            

   void foo(Class X) throws Exception {  
      if(X!=A.class)
         throw new Exception("Incorrect parameter of "+this.getClass().getName()+".foo("+X.getName()+")");
      y++;
      System.out.printf("A.foo(%s): y=%d\n",X.getName(),y);
   }
   void foo() throws Exception { 
      System.out.printf("A.foo()\n");
      this.foo(this.getClass()); 
   }
}

class B extends A {     
   int y=20;            

   @Override
   void foo(Class X) throws Exception { 
      if(X==B.class) { 
         y++; 
         System.out.printf("B.foo(%s): y=%d\n",X.getName(),y);
      } else { 
         System.out.printf("B.foo(%s) calls B.super.foo(%s)\n",X.getName(),X.getName());
         super.foo(X);
      } 
   }
}

class C extends B {     
   int y=30;            

   @Override
   void foo(Class X) throws Exception { 
      if(X==C.class) { 
         y++; 
         System.out.printf("C.foo(%s): y=%d\n",X.getName(),y);
      } else { 
         System.out.printf("C.foo(%s) calls C.super.foo(%s)\n",X.getName(),X.getName());
         super.foo(X);
      } 
   }

   void DoIt() {
      try {
         System.out.printf("DoIt: foo():\n");
         foo();         
         Show();

         System.out.printf("DoIt: foo(B):\n");
         foo(B.class);  
         Show();

         System.out.printf("DoIt: foo(A):\n");
         foo(A.class);  
         Show();
      } catch(Exception e) {
         //...
      }
   }

   void Show() {
      System.out.printf("Show: A.y=%d, B.y=%d, C.y=%d\n\n", ((A)this).y, ((B)this).y, ((C)this).y);
   }
} 

ผลลัพธ์ objC.DoIt ()

DoIt: foo():
A.foo()
C.foo(C): y=31
Show: A.y=10, B.y=20, C.y=31

DoIt: foo(B):
C.foo(B) calls C.super.foo(B)
B.foo(B): y=21
Show: A.y=10, B.y=21, C.y=31

DoIt: foo(A):
C.foo(A) calls C.super.foo(A)
B.foo(A) calls B.super.foo(A)
A.foo(A): y=11
Show: A.y=11, B.y=21, C.y=31

ใน C # จะใช้งานได้เฉพาะกับวิธีการที่ไม่ใช่เสมือนจริงและเนื่องจากวิธีการทั้งหมดเป็นเสมือนจริงใน Java จึงไม่แตกต่างกัน
Agent_L

0

มันง่ายที่จะทำ ตัวอย่างเช่น

คลาสย่อย C ของคลาสย่อย B และ B ของ A ทั้งสามมีเมธอด methodName ()

public abstract class A {

    public void methodName() {
        System.out.println("Class A");
    }

}

public class B extends A {

    public void methodName() {
        super.methodName();
        System.out.println("Class B");
    }

    // Will call the super methodName
    public void hackSuper() {
        super.methodName();
    }

}

public class C extends B {

    public static void main(String[] args) {
        A a = new C();
        a.methodName();
    }

    @Override
    public void methodName() {
        /*super.methodName();*/
        hackSuper();
        System.out.println("Class C");
    }

}

เรียกใช้คลาส C เอาต์พุตจะเป็น: คลาส A คลาส C

แทนผลลัพธ์: Class A คลาส B คลาส C


-1
public class SubSubClass extends SubClass {

    @Override
    public void print() {
        super.superPrint();
    }

    public static void main(String[] args) {
        new SubSubClass().print();
    }
}

class SuperClass {

    public void print() {
        System.out.println("Printed in the GrandDad");
    }
}

class SubClass extends SuperClass {

    public void superPrint() {
        super.print();
    }
}

ผลลัพธ์: พิมพ์ใน GrandDad


2
คำตอบนี้อยู่นอกขอบเขตของคำถาม OP ไม่ได้ถามวิธีเรียกวิธีการในคลาสปู่ย่าตายาย ปัญหาคือการอภิปรายว่าทำไมsuper.super.method()รหัสไม่ถูกต้องใน Java
Jed Schaaf

-1

คีย์เวิร์ด super เป็นวิธีเรียกใช้เมธอดใน superclass ในบทช่วยสอน Java: https://docs.oracle.com/javase/tutorial/java/IandI/super.html

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

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

นี่คือตัวอย่าง:

class Animal {
    public void doSth() {
        System.out.println(this);   // It's a Cat! Not an animal!
        System.out.println("Animal do sth.");
    }
}

class Cat extends Animal {
    public void doSth() {
        System.out.println(this);
        System.out.println("Cat do sth.");
        super.doSth();
    }
}

เมื่อคุณโทรcat.doSth()วิธีการdoSth()ในชั้นเรียนAnimalจะพิมพ์thisและมันเป็นแมว

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