Pergunta

Eu leio essa questão e pensei que isso seria facilmente resolvido (não que não seja solucionável sem) se alguém pudesse escrever:

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

Não tenho certeza se é útil em muitos casos, mas me pergunto por que não é e se algo assim existe em outros idiomas.

O que é que vocês acham?

EDITAR:Esclarecer:sim, eu sei, isso é impossível em Java e realmente não sinto falta disso.Isso não era nada que eu esperava que funcionasse e fiquei surpreso ao receber um erro do compilador.Acabei de ter a ideia e gostaria de discuti-la.

Foi útil?

Solução

Viola o encapsulamento. Você não deve ser capaz de ignorar o comportamento da classe pai. Faz sentido às vezes ser capaz de ignorar seu ter O comportamento da classe (particularmente do mesmo método), mas não do seu pai. Por exemplo, suponha que tenhamos uma "coleção de itens" base, uma subclasse que representa "uma coleção de itens vermelhos" e uma subclasse do que representa "uma coleção de itens vermelhos grandes". Faz sentido ter:

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

Isso é bom - os RedItems sempre podem estar confiantes de que os itens que ele contém são todos vermelhos. Agora suponha que nós nós estamos capaz de chamar 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);
    }
}

Agora poderíamos acrescentar o que gostarmos e o invariante em RedItems está quebrado.

Isso faz sentido?

Outras dicas

Acho que Jon Skeet tem a resposta correta.Eu só gostaria de acrescentar que você pode acessar variáveis ​​sombreadas de superclasses de superclasses lançando 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();
        }
}

que produz a saída:

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

(exemplo do JLS)

No entanto, isso não funciona para chamadas de método porque as chamadas de método são determinadas com base no tipo de tempo de execução do objeto.

Eu acho que o código a seguir permite usar super.super ... super.method () na maioria dos casos. (Mesmo que seja UGGLY fazer isso)

Resumidamente

  1. Crie uma instância temporária do tipo ancestral
  2. copiar valores de campos de original objetar a um temporário
  3. Invocar o método de destino em objeto temporário
  4. copiar valores modificados de volta ao objeto original

Uso:

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

Não tenho reputação suficiente para comentar, então adicionarei isso às outras respostas.

Jon Skeet responde excelentemente, com um belo exemplo. Matt B tem um ponto: nem todas as superclasses têm supers. Seu código quebraria se você chamasse um super super que não tinha super.

A programação orientada a objetos (que Java é) tem tudo a ver com objetos, não funções. Se você deseja programação orientada para a tarefa, escolha C ++ ou outra coisa. Se o seu objeto não se encaixar na super classe, você precisará adicioná -lo à "classe dos avós", criar uma nova classe ou encontrar outro super em que se encaixe.

Pessoalmente, achei essa limitação um dos maiores pontos fortes de Java. O código é um pouco rígido em comparação com outros idiomas que usei, mas sempre sei o que esperar. Isso ajuda no objetivo "simples e familiar" do Java. Na minha opinião, chamar Super.super não é simples ou familiar. Talvez os desenvolvedores se sentissem iguais?

Há algumas boas razões para fazer isso. Você pode ter uma subclasse que possui um método que é implementado incorretamente, mas o método pai é implementado corretamente. Por pertencer a uma biblioteca de terceiros, você pode não poder/não querendo alterar a fonte. Nesse caso, você deseja criar uma subclasse, mas substituir um método para chamar o método super.super.

Como mostrado por alguns outros pôsteres, é possível fazer isso através da reflexão, mas deve ser possível fazer algo como

(Supersuperclass this) .Themethod ();

Estou lidando com esse problema agora - a correção rápida é copiar e colar o método da superclasse no método da subsubclasse :)

Além dos bons pontos que outros fizeram, acho que há outro motivo: e se a superclasse não tiver uma superclasse?

Já que toda aula se estende naturalmente (pelo menos) Object, super.whatever() sempre se referirá a um método na superclasse. Mas e se sua classe apenas se estender Object - O que seria super.super Consulte então? Como esse comportamento deve ser tratado - um erro do compilador, um nullpointer, etc?

Eu acho que a principal razão pela qual isso não é permitido é que ele viole o encapsulamento, mas isso também pode ser um pequeno motivo.

Eu acho que se você substitui um método e deseja toda a versão super classe (como, digamos para equals), então você praticamente sempre deseja chamar a versão Direct Superclass First, que chamará sua versão de superclass, por sua vez, se quiser.

Eu acho que isso apenas faz sentido (se é que existe. Não consigo pensar em um caso em que o faça) chamar uma versão arbitrária da superclasse de um método. Não sei se isso é possível em Java. Isso pode ser feito em C ++:

this->ReallyTheBase::foo();

Em um palpite, porque não é usado com tanta frequência. A única razão pela qual pude ver o uso é se seu pai direto substituiu alguma funcionalidade e você está tentando restaurá -lo de volta ao original.

O que me parece ser contra os princípios da OO, uma vez que os pais diretos da classe devem estar mais intimamente relacionados à sua classe do que o avô.

Eu colocaria o corpo do método super.super em outro método, se possível

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

Ou se você não pode mudar a classe Super-Super, pode tentar o seguinte:

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

Nos dois casos, o

new ChildClass().toString();

resultados para "eu sou super"

Olhe para isto Projeto Github, especialmente a variável objecthandle. Este projeto mostra como realmente chamar e com precisão o método do avô em um neto.

Apenas caso o link seja quebrado, aqui está o código:

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

Codificação feliz !!!!

Parece ser possível obter pelo menos a classe da superclasse da superclasse, embora não necessariamente a instância, usando a reflexão; Se isso pode ser útil, considere o javadoc em http://java.sun.com/j2se/1.5.0/docs/api/java/lang/class.html#getsuperclass ()

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

     }
}

Run: uma construção bem -sucedida (tempo total: 0 segundos)

Cham uma chamada de super.super.method () faz sentido quando você não pode alterar o código da classe base. Isso geralmente acontece quando você está estendendo uma biblioteca existente.

Pergunte a si mesmo primeiro, por que você está estendendo essa aula? Se a resposta for "porque eu não posso alterá -lo", você pode criar pacote e classe exatos em seu aplicativo e reescrever o método safado ou criar delegado:

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

Por exemplo, você pode criar org.springframework.test.context.junit4.springjunit4classrunner classe em seu aplicativo para que essa classe seja carregada antes da real da JAR. Em seguida, reescreva métodos ou construtores.

Atenção: Este é um hack absoluto, e não é altamente recomendável usar, mas está funcionando! O uso dessa abordagem é perigoso devido a possíveis problemas com os carregadores de classe. Além disso, isso pode causar problemas cada vez que você atualizará a biblioteca que contém classe substituída.

Eu tive situações como essas quando a arquitetura é construir funcionalidade comum em uma base personalizada comum que implementa em nome de várias classes derivadas. No entanto, precisamos contornar a lógica comum para um método específico para uma classe derivada específica. Nesses casos, devemos usar uma implementação super.super.methodx.

Conseguimos isso introduzindo um membro booleano no CustomBaseClass, que pode ser usado para adiar seletivamente a implementação personalizada e o rendimento para a implementação da estrutura padrão, quando desejável.

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

No entanto, com bons princípios de arquitetura seguidos na estrutura e no aplicativo, poderíamos evitar essas situações facilmente, usando a abordagem HASA, em vez da abordagem do ISA. Mas, em todos os momentos, não é muito prático esperar uma arquitetura bem projetada em vigor e, portanto, a necessidade de se afastar dos princípios sólidos de design e introduzir hacks como esse. Apenas meus 2 centavos ...

@Jon skeet boa explicação. IMO se alguém quiser chamar o método super.super, deve -se querer ignorar o comportamento do pai imediato, mas deseja acessar o comportamento dos Grandes Pais. Isso pode ser alcançado através da instância de. Como abaixo do código

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

Aqui está a aula de motorista,

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

A saída disso será

In C Class
In A Class

O comportamento da classe B PrintClass será ignorado neste caso. Não tenho certeza sobre isso é uma prática ideal ou boa para obter super.uper, mas ainda está funcionando.

Se você acha que precisará da superclasse, poderá fazer referência a uma variável para essa classe. Por exemplo:

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

Deve imprimir:

2
1
0

IMO, é uma maneira limpa de alcançar super.super.sayYourName() comportamento em 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);
    }
}

Resultado:

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

Eu acho que isso é um problema que quebra o acordo de herança.
Ao estender uma aula que você obedece / concorda seu comportamento, recursos
Enquanto quando ligar super.super.method(), você quer quebrar seu próprio acordo de obediência.

Você simplesmente não pode escolher a super classe.

No entanto, pode acontecer situações quando você sente a necessidade de ligar super.super.method() - Geralmente, um sinal de design ruim, no seu código ou no código que você herda!
Se o super e super super As aulas não podem ser refatórias (algum código legado) e opte pela composição por herança.

A quebra de encapsulamento é quando você @Sobrepor Alguns métodos quebrando o código encapsulado. Os métodos projetados para não serem substituídos estão marcados final.

Em C#, você pode chamar um método de qualquer ancestral como este:

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

Além disso, você pode fazer isso em Delphi:

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

Mas em Java você pode fazer esse foco apenas por algum equipamento. Uma maneira possível é:

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 () Resultado Saída:

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

É simplesmente fácil de fazer. Por exemplo:

C Subclasse de B e B Subclasse de A. Ambos os três têm métodos MethodName (), por exemplo.

Public Abstract Classe A {

public void methodName() {
    System.out.println("Class A");
}

}

A classe pública B estende um {

public void methodName() {
    super.methodName();
    System.out.println("Class B");
}

// Will call the super methodName
public void hackSuper() {
    super.methodName();
}

}

public classe C estende 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");
}

}

Executar a saída da classe C será: Classe A Classe C

Em vez de saída: classe A classe B Classe C

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

Saída: Impresso no avô

A palavra -chave Super é apenas uma maneira de invocar o método na superclasse. No tutorial Java:https://docs.oracle.com/javase/tutorial/java/iandi/super.html

Se o seu método substituir um dos métodos de sua superclasse, você poderá invocar o método substituído através do uso da palavra -chave Super.

Não acredite que é uma referência do super objeto !!! Não, é apenas uma palavra -chave chamar métodos na superclasse.

Aqui está um exemplo:

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

Quando Você ligar cat.doSth(), o método doSth() em aula Animal irá imprimir this E é um gato.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top