Почему супер.супер.метод (); не разрешено в Java? - PullRequest
337 голосов
/ 25 февраля 2009

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

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

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

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

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

Ответы [ 22 ]

458 голосов
/ 25 февраля 2009

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

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 нарушен.

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

67 голосов
/ 25 февраля 2009

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

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

38 голосов
/ 22 апреля 2010

Я думаю, что следующий код позволяет использовать 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);
    }
}
11 голосов
/ 25 февраля 2009

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

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

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

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

7 голосов
/ 07 мая 2010

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

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

(SuperSuperClass this) .theMethod ();

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

6 голосов
/ 25 февраля 2009

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

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

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

4 голосов
/ 25 февраля 2009

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

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

this->ReallyTheBase::foo();
3 голосов
/ 25 февраля 2009

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

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

2 голосов
/ 29 октября 2013

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

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

2 голосов
/ 15 августа 2018

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...