Java: Gọi một phương thức siêu sẽ gọi một phương thức bị ghi đè


94
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();
    }
}

sản lượng mong đợi của tôi:

subclass method1
superclass method1
superclass method2

sản lượng thực tế:

subclass method1
superclass method1
subclass method2

Tôi biết về mặt kỹ thuật là tôi đã ghi đè một phương thức công khai, nhưng tôi nhận ra rằng bởi vì tôi đang gọi siêu phương thức, bất kỳ lệnh gọi nào bên trong phương thức siêu cấp sẽ ở trong phương thức siêu cấp, điều này không xảy ra. Bất kỳ ý tưởng nào về cách tôi có thể làm cho nó xảy ra?


2
Tôi nghi ngờ bạn có thể muốn "thích sáng tác hơn là thừa kế".
Tom Hawtin - tackline

Câu trả lời:


79

Từ khóa superkhông "dính". Mọi cuộc gọi phương thức được xử lý riêng lẻ, vì vậy ngay cả khi bạn phải SuperClass.method1()gọi super, điều đó không ảnh hưởng đến bất kỳ lệnh gọi phương thức nào khác mà bạn có thể thực hiện trong tương lai.

Điều đó có nghĩa là không có cách nào trực tiếp để gọi SuperClass.method2()từ SuperClass.method1()mà không đi SubClass.method2()trừ khi bạn đang làm việc với một phiên bản thực tế của SuperClass.

Bạn thậm chí không thể đạt được hiệu quả mong muốn bằng cách sử dụng Reflection (xem tài liệu củajava.lang.reflect.Method.invoke(Object, Object...) ).

[EDIT] Dường như vẫn còn một số nhầm lẫn. Hãy để tôi thử một lời giải thích khác.

Khi bạn gọi foo(), bạn thực sự gọi this.foo(). Java chỉ cho phép bạn bỏ qua this. Trong ví dụ trong câu hỏi, loại thisSubClass.

Vì vậy, khi Java thực thi mã trong SuperClass.method1(), cuối cùng nó sẽ đếnthis.method2();

Việc sử dụng superkhông thay đổi trường hợp được trỏ tới this. Vì vậy, cuộc gọi đến SubClass.method2()từ đó thisthuộc loạiSubClass .

Có lẽ sẽ dễ hiểu hơn khi bạn tưởng tượng rằng Java được chuyển thisnhư một tham số ẩn đầu tiên:

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);
    }
}

Nếu bạn theo dõi ngăn xếp cuộc gọi, bạn có thể thấy điều đó thiskhông bao giờ thay đổi, nó luôn là cá thể được tạo trong main().


ai đó có thể vui lòng tải lên một sơ đồ của điều này (dự định chơi chữ) đi qua ngăn xếp không? cảm ơn trước!
laycat

2
@laycat: Không cần sơ đồ. Chỉ cần nhớ rằng Java không có "bộ nhớ" cho super. Mỗi khi nó gọi một phương thức, nó sẽ xem xét kiểu thể hiện và bắt đầu tìm kiếm phương thức có kiểu này, bất kể bạn đã gọi bao lâu super. Vì vậy, khi bạn gọi method2một phiên bản của SubClass, nó sẽ luôn nhìn thấy một phiên bản từ SubClassđầu tiên.
Aaron Digulla

@AaronDigulla, Bạn có thể giải thích thêm về "Java không có bộ nhớ cho siêu"?
MengT

@ Truman'sworld: như tôi đã nói trong câu trả lời của mình: việc sử dụng superkhông thay đổi trường hợp. Nó không đặt một số trường ẩn "kể từ bây giờ, tất cả các cuộc gọi phương thức sẽ bắt đầu sử dụng SuperClass". Hay nói một cách khác: Giá trị của thiskhông thay đổi.
Aaron Digulla

@AaronDigulla, vậy điều đó có nghĩa là từ khóa super thực sự gọi các phương thức kế thừa trong lớp con thay vì chuyển đến lớp siêu?
MengT

15

Bạn chỉ có thể truy cập các phương thức được ghi đè trong các phương thức ghi đè (hoặc trong các phương thức khác của lớp ghi đè).

Vì vậy: không ghi đè method2()hoặc gọi super.method2()bên trong phiên bản được ghi đè.


8

Bạn đang sử dụng thistừ khóa thực sự đề cập đến "phiên bản hiện đang chạy của đối tượng bạn đang sử dụng", nghĩa là bạn đang gọi this.method2();trên lớp cha của mình, nghĩa là nó sẽ gọi phương thức2 () trên đối tượng bạn ' đang sử dụng, là SubClass.


8
đúng, và không sử dụng thiscũng không giúp được gì. Một lời gọi không đủ tiêu chuẩn ngầm sử dụngthis
Sean Patrick Floyd

3
Tại sao điều này được ủng hộ? Đây không phải là câu trả lời cho câu hỏi này. Khi bạn viết method2()trình biên dịch sẽ thấy this.method2(). Vì vậy, ngay cả khi bạn loại bỏ thisnó vẫn sẽ không hoạt động. Những gì @Sean Patrick Floyd đang nói là chính xác
Shervin Asgari

4
@Shervin anh ấy không nói bất cứ điều gì sai, anh ấy chỉ không làm cho rõ ràng những gì sẽ xảy ra nếu bạn bỏ quathis
Sean Patrick Floyd

4
Câu trả lời nằm ngay trong việc chỉ ra rằng thisđề cập đến "lớp cá thể chạy cụ thể" (được biết trong thời gian chạy) chứ không phải (như người đăng có vẻ tin tưởng) đến "lớp đơn vị biên dịch hiện tại" (nơi từ khóa được sử dụng, được biết đến trong thời gian biên dịch). Nhưng nó cũng có thể gây hiểu lầm (như Shervin chỉ ra): thiscũng được tham chiếu ngầm với lệnh gọi phương thức đơn giản; method2();cũng giống nhưthis.method2();
leonbloy

7

Tôi nghĩ về nó theo cách này

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

Hãy để tôi di chuyển lớp con đó sang trái một chút để tiết lộ những gì bên dưới ... (Trời ạ, tôi thích đồ họa 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).

Nói cách khác, trích dẫn từ Đặc tả ngôn ngữ Java :

Biểu mẫu super.Identifierđề cập đến trường có tên Identifiercủa đối tượng hiện tại, nhưng với đối tượng hiện tại được xem như một thể hiện của lớp cha của lớp hiện tại.

Biểu mẫu T.super.Identifierđề cập đến trường có tên Identifiercủa thể hiện bao quanh từ vựng tương ứng với T, nhưng với đối tượng đó được xem như là một thể hiện của lớp cha của T.

Theo thuật ngữ của layman, thisvề cơ bản là một đối tượng (* đối tượng **; chính đối tượng mà bạn có thể di chuyển trong các biến), thể hiện của lớp được khởi tạo, một biến đơn giản trong miền dữ liệu; supergiống như một con trỏ tới một khối mã mượn mà bạn muốn được thực thi, giống như một lệnh gọi hàm đơn thuần và nó liên quan đến lớp nơi nó được gọi.

Do đó nếu bạn sử dụng super từ lớp cha, bạn sẽ nhận được mã từ lớp superduper [ông bà] được thực thi), trong khi nếu bạn sử dụng this(hoặc nếu nó được sử dụng ngầm) từ lớp cha, nó vẫn trỏ đến lớp con (vì không ai thay đổi nó - và không ai có thể).


2

Nếu bạn không muốn superClass.method1 gọi subClass.method2, hãy đặt method2 ở chế độ riêng tư để nó không thể bị ghi đè.

Đây là một gợi ý:

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");
  }
}

Nếu nó không hoạt động theo cách này, thì tính đa hình sẽ là không thể (hoặc ít nhất là thậm chí không hữu ích bằng một nửa).


2

Vì cách duy nhất để tránh một phương thức bị ghi đè là sử dụng từ khóa super , tôi đã nghĩ đến việc chuyển phương thức2 () từ SuperClass sang một lớp Cơ sở mới khác và sau đó gọi nó từ 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();
    }
}

Đầu ra:

subclass method1
superclass method1
superclass method2

2

this luôn đề cập đến đối tượng hiện đang thực thi.

Để minh họa thêm vấn đề này, đây là một bản phác thảo đơn giản:

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

Nếu bạn có một thể hiện của hộp bên ngoài, một Subclassđối tượng, bất cứ nơi nào bạn tình cờ mạo hiểm bên trong hộp, thậm chí vào Superclass'khu vực', nó vẫn là thể hiện của hộp bên ngoài.

Hơn nữa, trong chương trình này chỉ có một đối tượng được tạo ra từ ba lớp, vì vậy thischỉ có thể tham chiếu đến một thứ và đó là:

nhập mô tả hình ảnh ở đây

như được hiển thị trong Netbeans 'Heap Walker'.


2
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");
    }
}

kêu gọi

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

đầu ra

subclass method1
superclass method1
superclass method2


1

Tôi không tin rằng bạn có thể làm điều đó trực tiếp. Một cách giải quyết là có một triển khai nội bộ riêng của method2 trong lớp cha và gọi nó. Ví dụ:

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

từ khóa "this" đề cập đến tham chiếu lớp hiện tại. Điều đó có nghĩa là, khi nó được sử dụng bên trong phương thức, lớp 'hiện tại' vẫn là SubClass và do đó, câu trả lời được giải thích.


1

Tóm lại, điều này chỉ đến đối tượng hiện tại và phương thức gọi trong java về bản chất là đa hình. Vì vậy, việc lựa chọn phương thức để thực thi, hoàn toàn phụ thuộc vào đối tượng được trỏ bởi nó. Do đó, việc gọi phương thức method2 () từ lớp cha sẽ gọi method2 () của lớp con, vì nó trỏ đến đối tượng của lớp con. Định nghĩa của điều này không thay đổi, bất kể nó được sử dụng ở lớp nào.

Tái bút. không giống như các phương thức, các biến thành viên của lớp không phải là đa hình.


0

Trong quá trình nghiên cứu của tôi cho một trường hợp tương tự, tôi đã kết thúc bằng cách kiểm tra dấu vết ngăn xếp trong phương thức lớp con để tìm ra nơi gọi đến. Có lẽ có nhiều cách thông minh hơn để làm như vậy, nhưng nó phù hợp với tôi và đó là một cách tiếp cận năng động.

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
        }
}

Tôi nghĩ câu hỏi để có hướng giải quyết cho vụ việc là hợp lý. Tất nhiên có nhiều cách để giải quyết vấn đề với các tên phương thức khác nhau hoặc thậm chí các loại tham số khác nhau, như đã được đề cập trong luồng, nhưng trong trường hợp của tôi, tôi không muốn nhầm lẫn bởi các tên phương thức khác nhau.


Mã này nguy hiểm và rủi ro và đắt tiền. Việc tạo ngoại lệ yêu cầu máy ảo phải tạo một stacktrace đầy đủ, việc so sánh chỉ dựa trên tên chứ không phải chữ ký đầy đủ là dễ xảy ra lỗi. Ngoài ra, nó còn có một lỗ hổng thiết kế lớn.
M. le Rutte

Từ quan điểm hiệu suất, mã của tôi dường như không tạo ra nhiều tác động hơn 'HashMap mới (). Size ()'. Tuy nhiên, tôi có thể đã bỏ qua những lo ngại mà bạn đang nghĩ đến và tôi không phải là một chuyên gia VM. Tôi thấy nghi ngờ của bạn bằng cách so sánh các tên lớp, nhưng nó bao gồm gói điều khiến tôi khá chắc chắn, đó là lớp cha của tôi. Dù sao, tôi thích Ý tưởng so sánh chữ ký thay thế, bạn sẽ làm điều đó như thế nào? Nói chung, nếu bạn có cách dễ dàng hơn để xác định xem người gọi là lớp cha hay bất kỳ ai khác, tôi sẽ đánh giá cao ở đây.
Đánh bại Siegrist

Nếu bạn cần xác định xem người gọi có phải là một người siêu hạng hay không, tôi sẽ nghiêm túc suy nghĩ lâu hơn nếu một thiết kế lại được thực hiện. Đây là một mô hình chống.
M. le Rutte

Tôi thấy điểm nhưng yêu cầu chung của chủ đề là hợp lý. Trong một số tình huống, có thể hiểu một lời gọi phương thức lớp cha vẫn ở trong ngữ cảnh lớp cha với bất kỳ lời gọi phương thức lồng nhau nào. Tuy nhiên, dường như không có cách nào để định hướng cuộc gọi phương thức tương ứng trong Superclass.
Đánh bại Siegrist

0

Mở rộng hơn nữa đầu ra của câu hỏi đã nêu, điều này sẽ cung cấp thêm thông tin chi tiết về công cụ chỉ định truy cập và hành vi ghi đè.

            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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.