Почему супер.супер.метод (); не разрешено в Java?


360

Я прочитал этот вопрос и подумал, что его легко решить (а не без него), если бы можно было написать:

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

Я не уверен, что это полезно во многих случаях, но мне интересно, почему это не так, и если что-то подобное существует на других языках.

Что вы думаете, ребята?

РЕДАКТИРОВАТЬ: Чтобы уточнить: да, я знаю, это невозможно в Java, и я действительно не скучаю по этому. Это ничего, что я ожидал работать и был удивлен, получив ошибку компилятора. У меня просто была идея, и я хотел бы обсудить это.


7
Желание вызова super.super.toString()противоречит вашему собственному решению, когда вы решаете расширить класс, принимая все (а не некоторые) его функции.
DayaMoon

Ответы:


484

Это нарушает инкапсуляцию. Вы не должны быть в состоянии обойти поведение родительского класса. Иногда имеет смысл обходить поведение вашего собственного класса (особенно в рамках одного и того же метода), но не поведения ваших родителей. Например, предположим, что у нас есть базовая «коллекция предметов», подкласс, представляющий «коллекцию красных предметов», и подкласс этого класса, представляющий «коллекцию больших красных предметов». Имеет смысл иметь:

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
хороший пример но я думал, что это плохой дизайн, когда базовый класс принимает элементы, но производный класс отклоняет их, потому что производный не может быть использован в качестве замены для базового класса (нарушение принципа замены). это правильное мышление или такая иерархия чиста?
Йоханнес Шауб -

5
Вы имеете в виду композицию, а не наследование? Этот пример не является причиной, чтобы избежать наследования - хотя есть много других.
Джон Скит

3
@Tim: Это был простой для понимания пример нарушения инкапсуляции. Это может быть установка свойства вместо этого. Кроме того, не все аспекты будут видны на уровне типа. Дженерики не ответ на все вопросы.
Джон Скит

12
@ JohannesSchaub-litb Я думаю, что это нарушает принцип подстановки Лискова, поскольку если кто-то кодирует контракт Item и использует экземпляр RedItems, он получит неожиданное исключение NotRedItemException. Меня всегда учили, что подкласс должен принимать супер набор входных данных и возвращать поднабор выходных данных. Другими словами, подкласс никогда не должен отклонять входные данные, которые являются допустимыми для суперкласса, или создавать выходные данные, которые суперкласс не может выдавать, это включает выдачу ошибки, которую суперкласс не генерирует.
Константин Таращанский

4
@piechuckerr: Иногда книги, иногда сообщения в блоге, иногда просто опыт ...
Джон Скит

72

Я думаю, что у Джона Скита правильный ответ. Я просто хотел бы добавить, что вы можете получить доступ к теневым переменным из суперклассов суперклассов, приведя 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();
        }
}

который производит вывод:

х = 3
super.x = 2
((T2) это) .x = 2
((T1) это) .x = 1
((Я) это) .x = 0

(пример из JLS )

Однако это не работает для вызовов методов, потому что вызовы методов определяются на основе типа времени выполнения объекта.


6
Если у вас есть переменная с тем же именем, что и в суперклассе, и по какой-то причине вы не можете (или не можете) изменить ее, вы все равно можете получить доступ к переменной суперкласса с тем же именем. В чем польза, спросите вы? Ну ... Я никогда не использовал это.
Майкл Майерс

Отличная ссылка на документ выражений. Несколько хороших примеров для людей, обучающихся в OCPJP.
Гордон

Потрясающие. Я бы никогда не подумал об использовании актерского состава.
Томас Эдинг

почему класс T1 переопределил переменную x? его статический финал по умолчанию не так ли?
Амарнат Хариш

@amarnathharish: он не переопределяется, и поля по умолчанию не являются финальными.
Майкл Майерс

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 (), тогда как вызов недоступного super.super привел бы к использованию getContext на C.
INOR

Хм. В некоторых случаях, возможно, вы могли бы преодолеть возражение Инора с помощью динамических прокси ( javahowto.blogspot.co.uk/2011/12/… ), перенаправляя вызовы метода на исходный объект (синхронизируя переменные после каждого вызова)? Похоже, что для реализации прокси все требует реализации интерфейса. Кроме того, мне интересно, возможно ли, чтобы супер-суперкласс вызывал один из своих супер-супер-методов, в частности, и вам не нужно было бы перенаправлять их ...
Erhannis

В другом комментарии я сказал, что блокирование super.super.предлагает программистам находить новые, запутанные и вопиющие способы выстрелить себе в ногу в поисках обходного пути, это прекрасный пример этого, потому что ваши коллеги, вероятно, будут ненавидеть вас за то, что вы что-то пишете вот так, что они лично и буквально застрелят тебя в ногу. +1
Брэден Бест

11

У меня недостаточно репутации, чтобы комментировать, поэтому я добавлю это к другим ответам.

Джон Скит отвечает превосходно, с прекрасным примером. У Мэтта B есть пункт: не у всех суперклассов есть супер. Ваш код сломался бы, если бы вы назвали супер супер, у которого не было супер.

Объектно-ориентированное программирование (то есть Java) - это все об объектах, а не функциях. Если вы хотите программирование, ориентированное на задачи, выберите C ++ или что-то еще. Если ваш объект не вписывается в его суперкласс, вам нужно добавить его в «класс прародителя», создать новый класс или найти другой суперкласс, в который он вписывается.

Лично я обнаружил, что это ограничение - одна из самых сильных сторон Java. Код несколько жесткий по сравнению с другими языками, которые я использовал, но я всегда знаю, чего ожидать. Это помогает с «простой и знакомой» целью Java. На мой взгляд, назвать super.super не просто и не привычно. Возможно, разработчики чувствовали то же самое?


3
Вы говорите: «Не у всех суперклассов есть супер». Ну, все, кроме java.lang.Object, что может дать вам «ноль». Так что я бы сказал, почти у всех есть супер.
Тим Бюте

Каждый класс, который пишет программист приложения, имеет «супер» (java.lang.Object нет, но программист приложения этого не пишет.)
finnw

2
Легко решается, если сделать super.super.super ... super ошибку времени компиляции, если слишком много supers. Учитывая, что Java имеет только публичное наследование, если кто-то изменил иерархию наследования, вы измените интерфейс. Так что я бы не беспокоился, что super ^ n это страшно.
Томас Эдинг

7

Есть несколько веских причин для этого. У вас может быть подкласс, у которого есть метод, который реализован неправильно, но родительский метод реализован правильно. Поскольку она принадлежит сторонней библиотеке, вы не можете / не хотите менять источник. В этом случае вы хотите создать подкласс, но переопределить один метод для вызова метода super.super.

Как показали некоторые другие плакаты, это можно сделать с помощью размышлений, но должно быть возможно сделать что-то вроде

(SuperSuperClass this) .theMethod ();

Я имею дело с этой проблемой прямо сейчас - быстрое решение состоит в том, чтобы скопировать и вставить метод суперкласса в метод подкласса :)


1
Приведение перед вызовом метода не меняет делегированный метод - всегда используется реализация подкласса, а не супер. См., Например, stackoverflow.com/questions/1677993/…
Джошуа Голдберг

1
@ Ларри, это именно та ситуация, в которой я находился, и именно то, что я использовал. Хороший звонок!
BCR

Если у вас есть код нужного метода, вы можете открыть все необходимые поля и запустить этот код в своем подклассе.
Enyby

6

В дополнение к очень хорошим замечаниям других, я думаю, есть еще одна причина: что, если у суперкласса нет суперкласса?

Поскольку каждый класс естественным образом расширяется (по крайней мере) Object, super.whatever()он всегда будет ссылаться на метод в суперклассе. Но что, если ваш класс только расширяется Object- что будет super.superозначать тогда? Как следует обрабатывать такое поведение - ошибка компилятора, NullPointer и т. Д.?

Я думаю, что основная причина, почему это не разрешено, заключается в том, что это нарушает инкапсуляцию, но это также может быть небольшой причиной.


4
Очевидно, это была бы ошибка компилятора - и это информация, которой располагает компилятор.
Майкл Боргвардт

4

Я думаю, что если вы переписываете метод и хотите использовать все его версии суперкласса (например, для equals), то вам практически всегда нужно сначала вызывать прямую версию суперкласса, которая, в свою очередь, будет вызывать его версию суперкласса. ,

Я думаю, что редко имеет смысл (если вообще. Я не могу придумать случай, когда это происходит) вызывать версию метода произвольного суперкласса. Я не знаю, возможно ли это вообще на Java. Это можно сделать в C ++:

this->ReallyTheBase::foo();

6
Имеет смысл, если кто-то написал подкласс с неправильно реализованным одним методом, но метод суперкласса выполняет 90% работы. Затем вы хотите создать подкласс и переопределить метод, вызывающий метод суперкласса суперкласса, и добавить свои собственные 10%.
Ларри Ватанабе

3

На предположение, потому что это не так часто используется. Единственная причина, по которой я мог его использовать, заключается в том, что ваш прямой родитель переопределил некоторые функции, и вы пытаетесь восстановить его обратно к исходному.

Мне кажется, что это противоречит принципам ОО, поскольку непосредственный родительский класс должен быть более тесно связан с вашим классом, чем дедушка и дедушка.


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

Удачного кодирования !!!!


Ваши ученые были так озабочены тем, могут ли они, или нет, они не переставали думать, должны ли они. Пожалуйста, на самом деле не делайте этого ...: P 🤔
Тим Бюте

Да, это слова кодера, а не мои. На мой взгляд, я думаю, что вам это может понадобиться когда-нибудь
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();

Результаты "Я супер супер"


Я просто оказался в ситуации, когда владею SuperSuperClass и ChildClass, но не SuperClass, поэтому я нашел первое решение полезным.
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);

     }
}

запустить: СТРОИТЬ УСПЕШНО (общее время: 0 секунд)


1
Я вижу, но вы создаете новый экземпляр, так что это не будет работать, если объект имеет какое-либо состояние.
Тим Бюте

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

@ Джон Скит Хорошее объяснение. 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

В этом случае поведение printClass класса B будет игнорироваться. Я не уверен, является ли это идеальной или хорошей практикой для достижения super.super, но все же это работает.


1
Ну, это креативно, но на самом деле не отвечает на мой вопрос. C по-прежнему не вызывает super.super, B просто ведет себя по-другому. Если вы можете изменить A и B, вы можете просто добавить другой метод вместо использования instanceof. Super.super.foo поможет вам в тех случаях, когда у вас нет доступа к A и B, и вы не можете изменить их.
Тим Бют

Согласитесь @TimButhe, но если кто-то хочет вызвать super.super, то он / она намеренно хочет игнорировать поведение родительского класса, так что вам просто нужно добиться этого с помощью существующего синтаксиса java. (Любой из вариантов, который вы хотите, это instanceof / другой метод)
Санджай Джейн

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
Ваш пример немного ... плохой, потому что вы используете статические методы. При использовании статических методов вам не нужна переменная или супер. Может быть, вы пропустили некоторую базовую концепцию ОО здесь, обязательно прочитайте об этом. Придется понизить, извините.
Тим Бюте

1
Это можно было бы легко сделать без статических методов. Это достаточно просто. Я собирался привести простой пример, как это сделать.
Ashtheking

Я понимаю, что хранение супер переменной в поле - это один из способов решить эту проблему. Но вам не нужна переменная, если это статический метод, вы можете просто использовать ее. Во-вторых, вызывать статические методы одна переменная - плохая практика, и большинство IDE предупредит вас об этом. Если вы исправите свой ответ, убрав статические вещи, я буду рад убрать мое понижение, без обид.
Тим Бюте

Вы близки, но ваш код не скомпилируется. Вы пытаетесь получить доступ к getNumber из статического контекста в вашем основном методе. Вы на самом деле пытались скомпилировать это? (а разве ваш UltraFoo не должен расширять SuperFoo?)
Тим Бьют

Я действительно не хочу быть жестоким с вами, но new UltraFoo.getNumber()не буду компилировать, так как вы пропустили круглые скобки. Тем не менее, я только что удалил свой donvote, так как концепция вашего кода теперь достаточно ясна, спасибо!
Тим Бюте

0

ИМО, это чистый способ достижения 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 (подкласс дочери) ...
bcr

Яков Файн прав. другими словами, это не очень хороший пример, потому что первоначальный вопрос был о вызове переопределенного метода в super.super.
INOR

0

Я думаю, что это проблема, которая нарушает соглашение о наследовании.
Расширяя класс, вы подчиняетесь / соглашаетесь с его поведением, функциями.
При вызове super.super.method()вы хотите разорвать собственное соглашение о послушании.

Вы просто не можете выбрать вишню из супер класса .

Однако могут возникнуть ситуации, когда вы чувствуете необходимость вызова super.super.method()- обычно это плохой знак дизайна, в вашем коде или в коде, который вы наследуете!
Если супер и супер супер классы не могут быть реорганизованы (некоторый унаследованный код), тогда выбирайте композицию вместо наследования. Нарушение

инкапсуляции - это когда вы переопределяете некоторые методы, нарушая инкапсулированный код. Методы, разработанные так, чтобы их нельзя было переопределить, помечаются как окончательные .


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

Вместо вывода: класс 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();
    }
}

Вывод: напечатано в дедуля


2
Этот ответ выходит за рамки вопроса. ОП не спрашивал, как вызвать метод в классе прародителя. Проблема заключается в обсуждении того, почему super.super.method()недопустимый код в Java.
Джед Шааф

-1

Ключевое слово super - это просто способ вызова метода в суперклассе. В руководстве по Java: https://docs.oracle.com/javase/tutorial/java/IandI/super.html

Если ваш метод переопределяет один из методов его суперкласса, вы можете вызвать переопределенный метод с помощью ключевого слова super.

Не верьте, что это ссылка на супер объект !!! Нет, это просто ключевое слово для вызова методов в суперклассе.

Вот пример:

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и это кошка.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.