Tại sao super.super.method (); không được phép trong Java?


360

Tôi đọc câu hỏi này và nghĩ rằng nó sẽ dễ dàng được giải quyết (không phải là nó không thể giải quyết được nếu không có) nếu người ta có thể viết:

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

Tôi không chắc nó có hữu ích trong nhiều trường hợp không, nhưng tôi tự hỏi tại sao nó không và nếu một cái gì đó như thế này tồn tại trong các ngôn ngữ khác.

các bạn nghĩ sao?

EDIT: Để làm rõ: vâng tôi biết, điều đó là không thể trong Java và tôi không thực sự bỏ lỡ nó. Điều này không có gì tôi mong đợi để làm việc và đã ngạc nhiên khi gặp lỗi trình biên dịch. Tôi chỉ có ý tưởng và muốn thảo luận về nó.


7
Muốn gọi super.super.toString()mâu thuẫn với quyết định của riêng bạn khi bạn chọn mở rộng một lớp do đó chấp nhận tất cả (không phải một số) tính năng của nó.
DayaMoon

Câu trả lời:


484

Nó vi phạm đóng gói. Bạn không thể bỏ qua hành vi của lớp cha. Nó làm cho tinh thần để thỉnh thoảng có thể bỏ qua của bạn riêng hành vi lớp học của (đặc biệt là từ bên trong cùng một phương pháp) nhưng không phải cha mẹ của bạn của. Ví dụ: giả sử chúng ta có một "bộ sưu tập vật phẩm" cơ bản, một lớp con đại diện cho "một bộ sưu tập các vật phẩm màu đỏ" và một lớp con đại diện cho "một bộ sưu tập các vật phẩm lớn màu đỏ". Nó có ý nghĩa để có:

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

Điều đó tốt - RedItems luôn có thể tự tin rằng các vật phẩm chứa trong đó đều có màu đỏ. Bây giờ giả sử chúng ta đã có thể gọi 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);
    }
}

Bây giờ chúng tôi có thể thêm bất cứ điều gì chúng tôi thích, và bất biến trong đó RedItemsbị phá vỡ.

Điều đó có ý nghĩa?


38
ví dụ tốt đẹp nhưng tôi nghĩ đó là thiết kế tồi khi lớp cơ sở chấp nhận các mục, nhưng lớp dẫn xuất từ ​​chối chúng, vì lớp dẫn xuất không thể được sử dụng như là một thay thế thả xuống cho lớp cơ sở (vi phạm nguyên tắc thay thế). Đó có phải là suy nghĩ đúng đắn, hay một hệ thống phân cấp như vậy sạch sẽ?
Julian Schaub - litb

5
Bạn có nghĩa là thành phần trên thừa kế? Ví dụ này không phải là lý do để tránh thừa kế - mặc dù có rất nhiều người khác.
Jon Skeet

3
@Tim: Đây chỉ là một ví dụ dễ hiểu về vi phạm đóng gói. Nó có thể được thiết lập một tài sản thay thế. Bên cạnh đó, không phải tất cả các khía cạnh sẽ được nhìn thấy ở cấp độ loại. Generics không phải là câu trả lời cho tất cả mọi thứ.
Jon Skeet

12
@ JohannesSchaub-litb Tôi nghĩ rằng nó vi phạm nguyên tắc thay thế Liskov, vì nếu một mã theo hợp đồng của Item và sử dụng một thể hiện của RedItems, người ta sẽ nhận được NotRedItemException bất ngờ. Tôi luôn được dạy rằng một lớp con nên có một bộ siêu đầu vào và trả về một tập hợp con của đầu ra. Nói cách khác, một lớp con không bao giờ nên từ chối một đầu vào hợp lệ cho siêu hạng hoặc tạo ra đầu ra mà siêu hạng đó không thể tạo ra, điều này bao gồm việc ném một lỗi mà siêu hạng không ném ra.
Konstantin Tarashchanskiy

4
@piechuckerr: Đôi khi sách, đôi khi bài đăng trên blog, đôi khi chỉ là trải nghiệm ...
Jon Skeet

72

Tôi nghĩ Jon Skeet có câu trả lời đúng. Tôi chỉ muốn thêm rằng bạn có thể truy cập các biến bị bóng từ các siêu lớp của siêu lớp bằng cách truyền 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();
        }
}

tạo ra đầu ra:

x = 3
siêu.x = 2
((T2) này) .x = 2
((T1) này) .x = 1
((Tôi) này) .x = 0

(ví dụ từ JLS )

Tuy nhiên, điều này không hoạt động đối với các cuộc gọi phương thức vì các cuộc gọi phương thức được xác định dựa trên loại thời gian chạy của đối tượng.


6
Nếu bạn có một biến có cùng tên với một trong siêu lớp và vì một số lý do bạn không thể (hoặc không được phép) thay đổi nó, bạn vẫn có thể truy cập biến của siêu lớp có cùng tên. Bạn sử dụng cái gì? Chà ... tôi chưa bao giờ sử dụng nó.
Michael Myers

Liên kết tuyệt vời đến các tài liệu biểu thức. Một số ví dụ hay cho những người học OCPJP.
Gordon

Tuyệt vời. Tôi sẽ không bao giờ nghĩ đến việc sử dụng một diễn viên.
Thomas Eding

Làm thế nào đến lớp T1 ghi đè biến x? cuối cùng tĩnh của nó phải không?
amarnath harish

@amarnathharish: Nó không bị ghi đè và các trường được bảo vệ không phải là gói cuối cùng theo mặc định.
Michael Myers

41

Tôi nghĩ rằng đoạn mã sau cho phép sử dụng super.super ... super.method () trong hầu hết các trường hợp. (ngay cả khi nó cố gắng làm điều đó)

Nói ngắn gọn

  1. tạo ví dụ tạm thời của kiểu tổ tiên
  2. sao chép giá trị của các trường từ đối tượng ban đầu sang tạm thời
  3. gọi phương thức đích trên đối tượng tạm thời
  4. sao chép các giá trị đã sửa đổi trở lại đối tượng ban đầu

Sử dụng :

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
Với sự phản chiếu bạn có thể làm bất cứ điều gì, vâng :) Bạn thậm chí có thể tạo các chuỗi có thể thay đổi.
BalusC

23
Thật là một điều khủng khiếp để làm! Tôi sẽ cho bạn +1 chỉ để tìm ra cách thực hiện nó :)
Larry Watanabe

6
Đó là một mẹo hay nhưng thậm chí không phải lúc nào cũng tương đương với việc gọi super.super không thể, nhưng cần thiết) và đó là vì lệnh super.super sẽ mang ngữ cảnh của C (C + B + A) trong khi câu trả lời của bạn tạo ra một ví dụ của A không có ngữ cảnh của B và C. Vì vậy, câu trả lời này sẽ không hoạt động nếu mỗi doThat gọi là getContext () và getContext được triển khai khác nhau trong mỗi lớp. trong câu trả lời của bạn, nó sẽ sử dụng getContext () của A trong khi gọi super.super không khả dụng sẽ dẫn đến việc sử dụng getContext của C.
vào

Hừm. Trong một số trường hợp, có lẽ bạn có thể vượt qua sự phản đối của chủ sở hữu với các proxy động ( javahowto.blogspot.co.uk/2011/12/ ,), chuyển hướng các cuộc gọi phương thức đến đối tượng ban đầu (đồng bộ hóa các biến sau mỗi cuộc gọi)? Dường như proxy yêu cầu mọi thứ phải được thực hiện một giao diện. Ngoài ra, tôi tự hỏi liệu lớp siêu siêu có thể gọi một trong những phương thức siêu siêu của nó, cụ thể là không, và bạn không cần phải chuyển hướng chúng ....
Erhannis

Tôi đã nói trong một bình luận khác rằng việc chặn các super.super.lập trình viên tìm ra những cách mới, hấp dẫn và nghiêm trọng để tự bắn vào chân mình để theo đuổi một cách giải quyết, đây là một ví dụ hoàn hảo về điều đó, bởi vì đồng nghiệp của bạn có thể sẽ ghét bạn vì đã viết một cái gì đó như thế này họ sẽ đích thân và bắn bạn vào chân. +1
Braden hay nhất

11

Tôi không có đủ danh tiếng để bình luận vì vậy tôi sẽ thêm câu này vào các câu trả lời khác.

Jon Skeet trả lời xuất sắc, với một ví dụ đẹp. Matt B có một điểm: không phải tất cả các siêu xe đều có supers. Mã của bạn sẽ bị hỏng nếu bạn gọi là siêu của một siêu không có siêu.

Lập trình hướng đối tượng (mà Java là) là tất cả về các đối tượng, không phải các hàm. Nếu bạn muốn lập trình theo định hướng nhiệm vụ, chọn C ++ hoặc một cái gì đó khác. Nếu đối tượng của bạn không phù hợp với siêu hạng của nó, thì bạn cần thêm nó vào "lớp ông bà", tạo một lớp mới hoặc tìm một siêu khác mà nó phù hợp.

Cá nhân tôi đã thấy giới hạn này là một trong những thế mạnh lớn nhất của Java. Mã có phần cứng nhắc so với các ngôn ngữ khác mà tôi đã sử dụng, nhưng tôi luôn biết những gì mong đợi. Điều này giúp với mục tiêu "đơn giản và quen thuộc" của Java. Trong suy nghĩ của tôi, gọi super.super không đơn giản hay quen thuộc. Có lẽ các nhà phát triển cảm thấy như vậy?


3
Bạn nói "không phải tất cả các siêu lớp đều có supers". Chà, tất cả trừ java.lang.Object những gì có thể cung cấp cho bạn "null". Vì vậy, tôi sẽ nói, gần như tất cả đều có bữa ăn tối.
Tim Büthe

Mỗi lớp mà lập trình viên ứng dụng viết có một "siêu" (java.lang.Object không, nhưng lập trình viên ứng dụng không viết điều đó.)
finnw

2
Dễ dàng giải quyết bằng cách tạo super.super.super ... siêu lỗi thời gian biên dịch nếu có quá nhiều supers. Xem xét Java chỉ có sự kế thừa công khai, nếu ai đó thay đổi hệ thống phân cấp thừa kế, bạn đang thay đổi giao diện. Vì vậy, tôi sẽ không lo lắng rằng siêu ^ n là đáng sợ.
Thomas Eding

7

Có một số lý do tốt để làm điều này. Bạn có thể có một lớp con có một phương thức được triển khai không chính xác, nhưng phương thức cha được thực hiện đúng. Vì nó thuộc về thư viện của bên thứ ba, bạn có thể không thể / không muốn thay đổi nguồn. Trong trường hợp này, bạn muốn tạo một lớp con nhưng ghi đè một phương thức để gọi phương thức super.super.

Như được hiển thị bởi một số áp phích khác, có thể thực hiện điều này thông qua sự phản chiếu, nhưng nó có thể làm một cái gì đó như

(SuperSuperClass this) .theMethod ();

Tôi đang xử lý vấn đề này ngay bây giờ - cách khắc phục nhanh là sao chép và dán phương thức siêu lớp vào phương thức lớp con :)


1
Truyền trước khi gọi một phương thức không thay đổi phương thức được ủy quyền cho Tập Nó luôn luôn là cách triển khai của lớp con được sử dụng, không bao giờ là siêu thực. Xem, ví dụ: stackoverflow.com/questions/1677993/ Kẻ
Joshua Goldberg

1
@Larry đây chính xác là tình huống tôi gặp phải, và chính xác là bản sửa lỗi tôi đã sử dụng. Cuộc gọi tốt
bcr

Nếu bạn có mã của phương thức cần thiết, bạn có thể mở tất cả các trường cần thiết và chạy mã này trong lớp con của bạn.
Enyby

6

Ngoài những điểm rất tốt mà những người khác đã đưa ra, tôi nghĩ còn có một lý do khác: nếu siêu lớp không có siêu lớp thì sao?

Vì mỗi lớp tự nhiên mở rộng (ít nhất) Object, super.whatever()sẽ luôn luôn đề cập đến một phương thức trong siêu lớp. Nhưng nếu lớp học của bạn chỉ kéo dài Object- những gì sẽsuper.super sau đó đề cập đến điều gì? Hành vi đó nên được xử lý như thế nào - lỗi trình biên dịch, NullPulum, v.v?

Tôi nghĩ lý do chính tại sao điều này không được phép là vì nó vi phạm đóng gói, nhưng đây cũng có thể là một lý do nhỏ.


4
Rõ ràng, đó sẽ là một lỗi trình biên dịch - và đó là thông tin mà trình biên dịch có.
Michael Borgwardt

4

Tôi nghĩ rằng nếu bạn ghi đè lên một phương thức và muốn tất cả các phiên bản siêu hạng của nó (như, nói cho equals ), thì bạn hầu như luôn muốn gọi phiên bản siêu lớp trực tiếp, trước tiên người ta sẽ gọi phiên bản siêu lớp của nó. .

Tôi nghĩ rằng nó chỉ hiếm khi có ý nghĩa (nếu có. Tôi không thể nghĩ ra trường hợp nào) để gọi một phiên bản siêu lớp tùy ý của một phương thức. Tôi không biết nếu điều đó là có thể trong Java. Nó có thể được thực hiện trong C ++:

this->ReallyTheBase::foo();

6
Thật có ý nghĩa nếu ai đó đã viết một lớp con với một phương thức được triển khai không chính xác nhưng phương thức siêu lớp thực hiện 90% công việc. Sau đó, bạn muốn tạo một lớp con và ghi đè phương thức gọi phương thức siêu lớp của lớp cha và thêm 10% của riêng bạn.
Larry Watanabe

3

Theo phỏng đoán, bởi vì nó không được sử dụng thường xuyên. Lý do duy nhất tôi có thể thấy khi sử dụng nó là nếu cha mẹ trực tiếp của bạn đã ghi đè một số chức năng và bạn đang cố gắng khôi phục nó trở lại ban đầu.

Tôi dường như chống lại các nguyên tắc OO, vì phụ huynh trực tiếp của lớp nên có liên quan chặt chẽ hơn với lớp của bạn hơn là ông bà.


3

Nhìn vào dự án Github này , đặc biệt là biến objectHandle. Dự án này cho thấy làm thế nào để thực sự và gọi chính xác phương pháp ông bà trên một đứa cháu.

Chỉ trong trường hợp liên kết bị hỏng, đây là mã:

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

Chúc mừng mã hóa !!!!


Các nhà khoa học của bạn đã rất bận tâm về việc họ có thể hay không, họ đã không ngừng suy nghĩ liệu họ có nên hay không. Xin đừng thực sự làm được điều này ...: P 🤔
Tim Buthe

Vâng, đó là những từ của lập trình viên, không phải của tôi. Theo ý kiến ​​của tôi, tôi nghĩ rằng bạn thực sự có thể cần nó vào một ngày nào đó
kyay

2

Tôi sẽ đặt thân phương thức super.super vào một phương thức khác, nếu có thể

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

Hoặc nếu bạn không thể thay đổi siêu siêu hạng, bạn có thể thử điều này:

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

Trong cả hai trường hợp,

new ChildClass().toString();

kết quả cho "Tôi siêu siêu"


Tôi chỉ thấy mình trong một tình huống mà tôi sở hữu SuperSuperClass và ChildClass chứ không phải SuperClass, vì vậy tôi thấy giải pháp đầu tiên hữu ích.
xofon


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

     }
}

chạy: THÀNH CÔNG BUILD (tổng thời gian: 0 giây)


1
Tôi hiểu, nhưng bạn đang tạo một thể hiện mới nên điều này sẽ không hoạt động nếu đối tượng có bất kỳ trạng thái nào.
Tim Büthe

1

Việc gọi super.super.method () có ý nghĩa khi bạn không thể thay đổi mã của lớp cơ sở. Điều này thường xảy ra khi bạn đang mở rộng một thư viện hiện có.

Hãy tự hỏi mình trước, tại sao bạn lại mở rộng lớp học đó? Nếu câu trả lời là "vì tôi không thể thay đổi nó" thì bạn có thể tạo gói và lớp chính xác trong ứng dụng của mình và viết lại phương thức nghịch ngợm hoặc tạo đại biểu:

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() {...}
    ...
}

Chẳng hạn, bạn có thể tạo lớp org.springframework.test.context.junit4.SpringJUnit4ClassRunner trong ứng dụng của mình để lớp này được tải trước lớp thực từ jar. Sau đó viết lại phương thức hoặc constructor.

Chú ý: Đây là bản hack tuyệt đối và không nên sử dụng nhưng nó HOẠT ĐỘNG! Sử dụng phương pháp này là nguy hiểm vì các vấn đề có thể xảy ra với trình nạp lớp. Ngoài ra, điều này có thể gây ra sự cố mỗi lần bạn sẽ cập nhật thư viện chứa lớp bị ghi đè.


1

Tôi đã có những tình huống như thế này khi kiến ​​trúc xây dựng chức năng chung trong CustomBaseClass chung, thực hiện thay mặt cho một số lớp dẫn xuất. Tuy nhiên, chúng ta cần phá vỡ logic chung cho phương thức cụ thể cho một lớp dẫn xuất cụ thể. Trong những trường hợp như vậy, chúng ta phải sử dụng triển khai super.super.methodX.

Chúng tôi đạt được điều này bằng cách giới thiệu một thành viên boolean trong CustomBaseClass, có thể được sử dụng để trì hoãn có chọn lọc việc thực hiện tùy chỉnh và mang lại triển khai khung mặc định khi muốn.

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

Tuy nhiên, với các nguyên tắc kiến ​​trúc tốt được tuân theo trong khung cũng như ứng dụng, chúng ta có thể tránh được các tình huống như vậy một cách dễ dàng, bằng cách sử dụng phương pháp hasA, thay vì phương pháp isA. Nhưng tại mọi thời điểm, nó không thực tế để mong đợi kiến ​​trúc được thiết kế tốt, và do đó cần phải thoát khỏi các nguyên tắc thiết kế vững chắc và giới thiệu các bản hack như thế này. Chỉ 2 xu của tôi ...


1

@Jon Skeet Giải thích hay. IMO nếu ai đó muốn gọi phương thức super.super thì người ta phải muốn bỏ qua hành vi của cha mẹ ngay lập tức, nhưng muốn truy cập vào hành vi cha mẹ lớn. Điều này có thể đạt được thông qua ví dụ. Như mã dưới đây

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

Đây là lớp tài xế,

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

Đầu ra của điều này sẽ là

In C Class
In A Class

Hành vi printClass lớp B sẽ bị bỏ qua trong trường hợp này. Tôi không chắc chắn về việc đây là một thực hành lý tưởng hay tốt để đạt được super.super, nhưng nó vẫn hoạt động.


1
Vâng, đó là sáng tạo nhưng không thực sự trả lời câu hỏi của tôi. C vẫn không gọi super.super, B chỉ cư xử khác. Nếu bạn có thể thay đổi A và B, bạn có thể chỉ cần thêm một phương thức khác thay vì sử dụng instanceof. Super.super.foo sẽ giúp bạn trong trường hợp bạn không có quyền truy cập vào A và B và không thể thay đổi chúng.
Tim Büthe

Đồng ý @TimButhe, nhưng nếu ai đó muốn gọi tới super.super thì anh ta / cô ta cố tình muốn bỏ qua hành vi của lớp cha, vì vậy bạn chỉ cần đạt được điều đó bằng cú pháp hiện có của java. (Bất kỳ tùy chọn nào bạn muốn hoặc là thể hiện / phương thức khác nhau)
Sanjay Jain

0

Nếu bạn nghĩ rằng bạn sẽ cần siêu lớp, bạn có thể tham chiếu nó trong một biến cho lớp đó. Ví dụ:

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

Nên in ra:

2
1
0

2
Sơ đồ của bạn là loại ... cũng tệ, bởi vì bạn sử dụng các phương thức tĩnh. Khi sử dụng các phương thức tĩnh, bạn hoàn toàn không cần biến hoặc siêu. Có thể bạn đã bỏ lỡ một số khái niệm OO cơ bản ở đây, hãy chắc chắn rằng bạn đã đọc về nó. Phải downvote, xin lỗi.
Tim Büthe

1
Nó có thể dễ dàng được thực hiện mà không cần phương pháp tĩnh. Nó đủ đơn giản. Tôi đã đi đến một ví dụ đơn giản về cách làm điều này.
Ashtheking

Tôi nhận được quan điểm của bạn, lưu trữ siêu biến trong một trường là một cách để giải quyết điều này. Nhưng, bạn không cần biến nếu đó là một phương thức tĩnh, bạn chỉ có thể sử dụng nó. Thứ hai, việc gọi các phương thức tĩnh một biến là một thực tiễn tồi và hầu hết các IDE sẽ cảnh báo bạn về điều đó. Nếu bạn sửa câu trả lời của mình, xóa nội dung tĩnh, tôi sẽ vui lòng xóa phần mềm của tôi xuống, không vi phạm.
Tim Büthe

Bạn đã đóng, nhưng mã của bạn sẽ không biên dịch. Bạn cố gắng truy cập getNumber từ một bối cảnh tĩnh trong phương thức chính của bạn. Bạn có thực sự cố gắng để biên dịch này? (và không nên UltraFoo của bạn mở rộng SuperFoo?)
Tim Büthe

Tôi thực sự không muốn tàn nhẫn với bạn, nhưng new UltraFoo.getNumber()sẽ không biên dịch, vì bạn đã bỏ lỡ dấu ngoặc đơn ở đó. Tuy nhiên, tôi vừa gỡ bỏ donvote của mình, vì khái niệm mã của bạn hiện khá rõ ràng, cảm ơn!
Tim Büthe

0

IMO, đó là một cách rõ ràng để đạt được super.super.sayYourName()hành vi trong 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);
    }
}

Đầu ra:

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


ah, vậy bạn đang ủng hộ việc thực hiện một hệ thống phân cấp lớp như thế này? Thật không may, điều này bắt đầu trở nên thực sự lộn xộn nếu bạn muốn truy cập phương thức trong Mama from Baby (lớp con của con gái) ...
bcr

yakov fain là đúng. nói cách khác, đó không phải là một ví dụ hay, bởi vì câu hỏi ban đầu là về việc gọi một phương thức được ghi đè trong super.super.
vào

0

Tôi nghĩ rằng đây là một vấn đề phá vỡ thỏa thuận thừa kế.
Bằng cách mở rộng một lớp bạn tuân theo / đồng ý hành vi của nó, các tính năng
Trong khi gọi super.super.method(), bạn muốn phá vỡ thỏa thuận vâng lời của chính mình.

Bạn không thể chọn anh đào từ siêu hạng .

Tuy nhiên, có thể xảy ra tình huống khi bạn cảm thấy cần phải gọi super.super.method()- thường là một dấu hiệu thiết kế xấu, trong mã của bạn hoặc trong mã bạn thừa kế!
Nếu siêusiêu siêu lớp không thể được refactored (một số mã di sản), sau đó lựa chọn cho các thành phần trên thừa kế.

Phá vỡ đóng gói là khi bạn @Override một số phương thức bằng cách phá mã được đóng gói. Các phương thức được thiết kế để không bị ghi đè được đánh dấu cuối cùng .


0

Trong C #, bạn có thể gọi một phương thức của bất kỳ tổ tiên nào như thế này:

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

Ngoài ra, bạn có thể làm điều này trong Delphi:

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

Nhưng trong Java, bạn có thể thực hiện việc tập trung như vậy chỉ bằng một số thiết bị. Một cách có thể là:

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

kết quả đầu ra 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

Trong C #, nó sẽ chỉ hoạt động đối với các phương thức không ảo và vì tất cả các phương thức đều là ảo trong Java nên nó không thực sự khác biệt.
Đặc vụ_L

0

Nó chỉ đơn giản là dễ dàng để làm. Ví dụ:

Lớp con C của lớp con B và B của A. Cả ba đều có phương thức methodName () chẳng hạn.

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

}

Chạy lớp C Kết quả sẽ là: Lớp A Lớp C

Thay vì đầu ra: Class A Class B Class 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();
    }
}

Đầu ra: In trong GrandDad


2
Câu trả lời này nằm ngoài phạm vi của câu hỏi. OP không hỏi làm thế nào để gọi một phương thức trong lớp ông bà. Vấn đề là một cuộc thảo luận về lý do tại sao super.super.method()không phải là mã hợp lệ trong Java.
Jed Schaaf

-1

Siêu từ khóa chỉ là một cách để gọi phương thức trong siêu lớp. Trong hướng dẫn Java: https://docs.oracle.com/javase/tutorial/java/IandI/super.html

Nếu phương thức của bạn ghi đè một trong các phương thức của lớp cha, bạn có thể gọi phương thức được ghi đè thông qua việc sử dụng siêu từ khóa.

Đừng tin rằng đó là một tài liệu tham khảo về siêu đối tượng !!! Không, nó chỉ là một từ khóa để gọi các phương thức trong siêu lớp.

Đây là một ví dụ:

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

Khi bạn gọi cat.doSth(), phương thức doSth()trong lớp Animalsẽ in thisvà đó là một con mèo.

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.