Java: การเรียกใช้ super method ซึ่งเรียกว่า overridden method


98
public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

ผลลัพธ์ที่คาดหวังของฉัน:

subclass method1
superclass method1
superclass method 2

ผลลัพธ์จริง:

subclass method1
superclass method1
subclass method 2

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


2
ฉันสงสัยว่าคุณอาจต้องการ "ชอบการแต่งเพลงมากกว่าการถ่ายทอดทางพันธุกรรม"
Tom Hawtin - แท็กไลน์

คำตอบ:


84

คีย์เวิร์ดsuperไม่ "ติด" การโทรทุกวิธีจะได้รับการจัดการแยกกันดังนั้นแม้ว่าคุณจะSuperClass.method1()โทรเข้ามาsuperแต่ก็ไม่ส่งผลต่อการเรียกวิธีอื่นใดที่คุณอาจทำได้ในอนาคต

ซึ่งหมายความว่าไม่มีทางตรงที่จะเรียกSuperClass.method2()จากSuperClass.method1()โดยไม่ต้องไป แต่ถ้าคุณกำลังทำงานร่วมกับอินสแตนซ์ที่แท้จริงของSubClass.method2()SuperClass

คุณไม่สามารถทำได้แม้กระทั่งเอฟเฟกต์ที่ต้องการโดยใช้ Reflection (ดูเอกสารประกอบjava.lang.reflect.Method.invoke(Object, Object...) )

[แก้ไข]ยังคงมีความสับสนอยู่ ให้ฉันลองใช้คำอธิบายอื่น

เมื่อคุณเรียกคุณจริงวิงวอนfoo() this.foo()Java ช่วยให้คุณละเว้นไฟล์this. ในตัวอย่างในคำถามประเภทของการมีthisSubClass

ดังนั้นเมื่อ Java รันโค้ดในSuperClass.method1()ที่สุดก็มาถึงที่this.method2();

ใช้ไม่ได้เปลี่ยนตัวอย่างที่ชี้ไปตามsuper thisดังนั้นการเรียกร้องไปSubClass.method2()ตั้งแต่เป็นประเภทthisSubClass

อาจจะง่ายกว่าที่จะเข้าใจเมื่อคุณจินตนาการว่า Java ส่งผ่านthisเป็นพารามิเตอร์แรกที่ซ่อนอยู่:

public class SuperClass
{
    public void method1(SuperClass this)
    {
        System.out.println("superclass method1");
        this.method2(this); // <--- this == mSubClass
    }

    public void method2(SuperClass this)
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1(SubClass this)
    {
        System.out.println("subclass method1");
        super.method1(this);
    }

    @Override
    public void method2(SubClass this)
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1(mSubClass);
    }
}

หากคุณติดตามกลุ่มการโทรคุณจะเห็นว่าthisไม่มีการเปลี่ยนแปลงนั่นคืออินสแตนซ์ที่สร้างขึ้นmain()เสมอ


ใครช่วยกรุณาอัพโหลดไดอะแกรมของสิ่งนี้ (เล่นสำนวนเจตนา) ผ่านสแต็กได้ไหม ขอบคุณล่วงหน้า!
laycat

2
@laycat: ไม่จำเป็นต้องมีแผนภาพ เพียงจำไว้ว่า Java ไม่มี "ความจำ" superสำหรับ ทุกครั้งที่เรียกเมธอดมันจะดูประเภทอินสแตนซ์และเริ่มค้นหาเมธอดด้วยประเภทนี้ไม่ว่าคุณจะโทรบ่อยแค่ไหนsuperก็ตาม ดังนั้นเมื่อคุณเรียกmethod2ใช้อินสแตนซ์SubClassมันจะเห็นอันSubClassแรกเสมอ
Aaron Digulla

@AaronDigulla คุณสามารถอธิบายเพิ่มเติมเกี่ยวกับ "Java ไม่มีหน่วยความจำสำหรับ super" ได้หรือไม่
MengT

@ Truman'sworld: อย่างที่ฉันพูดในคำตอบของฉัน: การใช้superไม่ได้เปลี่ยนอินสแตนซ์ ไม่ได้ตั้งค่าช่องที่ซ่อนไว้ "จากนี้ไปการเรียกวิธีการทั้งหมดควรเริ่มต้นโดยใช้SuperClass" หรือกล่าวอีกนัยหนึ่ง: ค่าของthisไม่เปลี่ยนแปลง
Aaron Digulla

@AaronDigulla นั่นหมายความว่าซูเปอร์คีย์เวิร์ดเรียกใช้เมธอดที่สืบทอดมาในคลาสย่อยแทนที่จะไปที่ซูเปอร์คลาสหรือไม่?
MengT

15

คุณสามารถเข้าถึงเมธอดที่ถูกลบล้างได้ในเมธอดการลบล้างเท่านั้น (หรือในเมธอดอื่นของคลาสการแทนที่

ดังนั้น: อย่าลบล้างmethod2()หรือเรียกsuper.method2()ภายในเวอร์ชันที่ถูกแทนที่


8

คุณกำลังใช้thisคีย์เวิร์ดที่อ้างถึง "อินสแตนซ์ที่กำลังทำงานอยู่ของอ็อบเจ็กต์ที่คุณกำลังใช้อยู่" นั่นคือคุณกำลังเรียกใช้this.method2();ซูเปอร์คลาสของคุณนั่นคือมันจะเรียก method2 () บนอ็อบเจ็กต์ที่คุณ ' กำลังใช้อยู่ซึ่งเป็น SubClass


9
จริงและไม่ใช้thisก็ไม่ช่วยเช่นกัน การเรียกร้องที่ไม่มีเงื่อนไขใช้โดยปริยายthis
ฌอนแพทริคฟลอยด์

3
เหตุใดจึงได้รับการโหวตเพิ่มขึ้น นี่ไม่ใช่คำตอบสำหรับคำถามนี้ เมื่อคุณเขียนคอมไพเลอร์ที่จะเห็นmethod2() this.method2()ดังนั้นแม้ว่าคุณจะลบออกthisก็ยังไม่ได้ผล สิ่งที่ @Sean Patrick Floyd พูดนั้นถูกต้อง
Shervin Asgari

4
@ เชอร์วินเขาไม่ได้พูดอะไรผิดเขาแค่ไม่ชัดเจนว่าจะเกิดอะไรขึ้นถ้าคุณออกไปthis
ฌอนแพทริคฟลอยด์

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

7

ฉันคิดแบบนี้

+----------------+
|     super      |
+----------------+ <-----------------+
| +------------+ |                   |
| |    this    | | <-+               |
| +------------+ |   |               |
| | @method1() | |   |               |
| | @method2() | |   |               |
| +------------+ |   |               |
|    method4()   |   |               |
|    method5()   |   |               |
+----------------+   |               |
    We instantiate that class, not that one!

ให้ฉันย้ายคลาสย่อยนั้นไปทางซ้ายเล็กน้อยเพื่อเปิดเผยว่ามีอะไรอยู่ข้างใต้ ... (ผู้ชายฉันชอบกราฟิก ASCII)

We are here
        |
       /  +----------------+
      |   |     super      |
      v   +----------------+
+------------+             |
|    this    |             |
+------------+             |
| @method1() | method1()   |
| @method2() | method2()   |
+------------+ method3()   |
          |    method4()   |
          |    method5()   |
          +----------------+

Then we call the method
over here...
      |               +----------------+
 _____/               |     super      |
/                     +----------------+
|   +------------+    |    bar()       |
|   |    this    |    |    foo()       |
|   +------------+    |    method0()   |
+-> | @method1() |--->|    method1()   | <------------------------------+
    | @method2() | ^  |    method2()   |                                |
    +------------+ |  |    method3()   |                                |
                   |  |    method4()   |                                |
                   |  |    method5()   |                                |
                   |  +----------------+                                |
                   \______________________________________              |
                                                          \             |
                                                          |             |
...which calls super, thus calling the super's method1() here, so that that
method (the overidden one) is executed instead[of the overriding one].

Keep in mind that, in the inheritance hierarchy, since the instantiated
class is the sub one, for methods called via super.something() everything
is the same except for one thing (two, actually): "this" means "the only
this we have" (a pointer to the class we have instantiated, the
subclass), even when java syntax allows us to omit "this" (most of the
time); "super", though, is polymorphism-aware and always refers to the
superclass of the class (instantiated or not) that we're actually
executing code from ("this" is about objects [and can't be used in a
static context], super is about classes).

กล่าวอีกนัยหนึ่งคือการอ้างอิงจากข้อกำหนดภาษา Java :

แบบฟอร์มsuper.Identifierอ้างถึงฟิลด์ที่ตั้งชื่อIdentifierของอ็อบเจ็กต์ปัจจุบัน แต่ด้วยอ็อบเจ็กต์ปัจจุบันที่มองว่าเป็นอินสแตนซ์ของซูเปอร์คลาสของคลาสปัจจุบัน

แบบฟอร์มที่T.super.Identifierหมายถึงข้อมูลที่มีชื่อIdentifierของอินสแตนซ์ lexically การปิดล้อมที่สอดคล้องกับTแต่มีอินสแตนซ์ที่มองว่าเป็นตัวอย่างของ superclass Tของที่

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

ดังนั้นหากคุณใช้superจากซูเปอร์คลาสคุณจะได้รับโค้ดจากคลาส superduper [ปู่ย่าตายาย] ที่ดำเนินการ) ในขณะที่ถ้าคุณใช้this(หรือถ้าใช้โดยปริยาย) จากซูเปอร์คลาสมันจะชี้ไปที่คลาสย่อย (เพราะไม่มีใครเปลี่ยนแปลง - และไม่มีใคร สามารถ)


3
class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        SuperClass se=new SuperClass();
        se.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }
}


class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

โทร

SubClass mSubClass = new SubClass();
mSubClass.method1();

เอาต์พุต

subclass method1
superclass method1
superclass method 2


2

ถ้าคุณไม่ต้องการให้ superClass.method1 เรียก subClass.method2 ให้กำหนด method2 ให้เป็นส่วนตัวเพื่อไม่ให้ถูกแทนที่

คำแนะนำมีดังนี้

public class SuperClass {

  public void method1() {
    System.out.println("superclass method1");
    this.internalMethod2();
  }

  public void method2()  {
    // this method can be overridden.  
    // It can still be invoked by a childclass using super
    internalMethod2();
  }

  private void internalMethod2()  {
    // this one cannot.  Call this one if you want to be sure to use
    // this implementation.
    System.out.println("superclass method2");
  }

}

public class SubClass extends SuperClass {

  @Override
  public void method1() {
    System.out.println("subclass method1");
    super.method1();
  }

  @Override
  public void method2() {
    System.out.println("subclass method2");
  }
}

หากไม่ได้ผลเช่นนี้ความหลากหลายจะเป็นไปไม่ได้ (หรืออย่างน้อยก็มีประโยชน์ไม่ถึงครึ่งหนึ่ง)


2

เนื่องจากวิธีเดียวที่จะหลีกเลี่ยงเมธอดในการลบล้างคือการใช้คีย์เวิร์ดsuperฉันจึงคิดที่จะย้าย method2 () จากSuperClassไปยังคลาสBaseใหม่อื่นแล้วเรียกใช้จากSuperClass :

class Base 
{
    public void method2()
    {
        System.out.println("superclass method2");
    }
}

class SuperClass extends Base
{
    public void method1()
    {
        System.out.println("superclass method1");
        super.method2();
    }
}

class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

เอาท์พุต:

subclass method1
superclass method1
superclass method2

2

this มักหมายถึงวัตถุที่กำลังดำเนินการอยู่เสมอ

เพื่ออธิบายประเด็นต่อไปนี้เป็นภาพร่างง่ายๆ:

+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

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

ยิ่งไปกว่านั้นในโปรแกรมนี้มีเพียงออบเจ็กต์เดียวที่ถูกสร้างขึ้นจากทั้งสามคลาสดังนั้นจึงthisสามารถอ้างถึงสิ่งเดียวเท่านั้นคือ:

ป้อนคำอธิบายภาพที่นี่

ดังที่แสดงใน'Heap Walker' ของNetbeans


1

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

public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.internalMethod2();
    }

    public void method2()
    {
        this.internalMethod2(); 
    }
    private void internalMethod2()
    {
        System.out.println("superclass method2");
    }

}

1

คีย์เวิร์ด "this" หมายถึงการอ้างอิงคลาสปัจจุบัน นั่นหมายความว่าเมื่อใช้ภายในเมธอดคลาส 'ปัจจุบัน' ยังคงเป็นคลาสย่อยดังนั้นคำตอบจะได้รับการอธิบาย


1

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

ปล. ไม่เหมือนกับวิธีการตัวแปรสมาชิกของคลาสไม่ใช่ความหลากหลาย


0

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

public void method2(){
        Exception ex=new Exception();
        StackTraceElement[] ste=ex.getStackTrace();
        if(ste[1].getClassName().equals(this.getClass().getSuperclass().getName())){
            super.method2();
        }
        else{
            //subclass method2 code
        }
}

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


รหัสนี้อันตรายและมีความเสี่ยงและมีราคาแพง การสร้างข้อยกเว้นต้องการให้ VM สร้างสแต็กเทรซแบบเต็มโดยเปรียบเทียบเฉพาะชื่อและลายเซ็นแบบเต็มไม่ได้มีโอกาสผิดพลาด นอกจากนี้ยังพบข้อบกพร่องในการออกแบบที่ยิ่งใหญ่
M. le Rutte

จากมุมมองด้านประสิทธิภาพโค้ดของฉันดูเหมือนจะไม่สร้างผลกระทบมากไปกว่า 'new HashMap (). size ()' อย่างไรก็ตามฉันอาจมองข้ามข้อกังวลที่คุณคิดไว้และฉันก็ไม่ใช่ผู้เชี่ยวชาญ VM ฉันเห็นข้อสงสัยของคุณโดยการเปรียบเทียบชื่อคลาส แต่รวมถึงแพ็คเกจที่ทำให้ฉันค่อนข้างแน่ใจว่าเป็นคลาสแม่ของฉัน อย่างไรก็ตามฉันชอบแนวคิดในการเปรียบเทียบลายเซ็นแทนคุณจะทำอย่างไร โดยทั่วไปหากคุณมีวิธีที่ราบรื่นกว่าในการตรวจสอบว่าผู้โทรเป็นซูเปอร์คลาสหรือใครก็ตามฉันจะขอบคุณที่นี่
Beat Siegrist

หากคุณต้องการตรวจสอบว่าผู้โทรเป็นซูเปอร์คลาสหรือไม่ฉันจะคิดอย่างจริงจังอีกต่อไปหากมีการออกแบบใหม่ นี่คือการต่อต้านรูปแบบ
M. le Rutte

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

0

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

            package overridefunction;
            public class SuperClass 
                {
                public void method1()
                {
                    System.out.println("superclass method1");
                    this.method2();
                    this.method3();
                    this.method4();
                    this.method5();
                }
                public void method2()
                {
                    System.out.println("superclass method2");
                }
                private void method3()
                {
                    System.out.println("superclass method3");
                }
                protected void method4()
                {
                    System.out.println("superclass method4");
                }
                void method5()
                {
                    System.out.println("superclass method5");
                }
            }

            package overridefunction;
            public class SubClass extends SuperClass
            {
                @Override
                public void method1()
                {
                    System.out.println("subclass method1");
                    super.method1();
                }
                @Override
                public void method2()
                {
                    System.out.println("subclass method2");
                }
                // @Override
                private void method3()
                {
                    System.out.println("subclass method3");
                }
                @Override
                protected void method4()
                {
                    System.out.println("subclass method4");
                }
                @Override
                void method5()
                {
                    System.out.println("subclass method5");
                }
            }

            package overridefunction;
            public class Demo 
            {
                public static void main(String[] args) 
                {
                    SubClass mSubClass = new SubClass();
                    mSubClass.method1();
                }
            }

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