это - полиморфизм java




Почему super.super.method(); не разрешено в Java? (15)

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

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

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

Что, вы парни, думаете?

EDIT: Прояснить: да, я знаю, это невозможно в Java, и я действительно не скучаю по нему. Это ничего, что я ожидал от работы, и был удивлен получением ошибки компилятора. У меня была идея и я хотел бы обсудить это.


@ Jon Skeet Хорошее объяснение. IMO, если кто-то хочет вызвать метод super.super, то нужно игнорировать поведение непосредственного родителя, но хотите получить доступ к поведению великого родителя. Это может быть достигнуто с помощью экземпляра. Как указано ниже

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

public class B extends A {

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

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

Вот класс драйвера,

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

Результатом этого будет

In C Class
In A Class

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


IMO, это чистый способ добиться поведения super.super.sayYourName() в Java.

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

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

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

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

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

Выход:

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


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

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

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


Вызов 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. Затем перепишите методы или конструкторы.

Внимание: это абсолютный взлом, и его НЕ рекомендуют использовать, но это РАБОТА! Использование этого подхода опасно из-за возможных проблем с загрузчиками классов. Также это может вызвать проблемы при каждом обновлении библиотеки, содержащей перезаписанный класс.


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

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

Казалось бы, по крайней мере, получить класс суперкласса суперкласса, хотя и не обязательно его экземпляр, используя отражение; если это может быть полезно, рассмотрите Javadoc по адресу http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass()


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

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


Посмотрите на this проект 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";
        }
    }
}

Счастливое кодирование !!!!


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

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

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

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


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

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 сломан.

Имеет ли это смысл?


Я бы поставил тело метода 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();

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


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

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

this->ReallyTheBase::foo();

Я думаю, что у Джона Скита есть правильный ответ. Я хотел бы добавить, что вы можете получить доступ к затененным переменным из суперклассов суперклассов, выполнив this :

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

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

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

x=              3
super.x=        2
((T2)this).x=   2
((T1)this).x=   1
((I)this).x=    0

(пример из JLS )

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


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

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

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

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


public class SubSubClass extends SubClass {

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

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

class SuperClass {

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

class SubClass extends SuperClass {

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

Выход: Отпечатано в GrandDad







superclass