Java: chamando um super método que chama um método substituído

94
public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

minha saída esperada:

subclasse método1
superclasse método1
superclasse método2

saída real:

subclasse método1
superclasse método1
subclasse método2

Sei que tecnicamente substituí um método público, mas percebi que, como estava chamando o super, todas as chamadas dentro do super permaneceriam no super, isso não está acontecendo. Alguma ideia de como posso fazer isso acontecer?

jsonfry
fonte
2
Eu suspeito que você pode querer "preferir composição à herança".
Tom Hawtin - tackline

Respostas:

79

A palavra-chave supernão "gruda". Cada chamada de método é tratada individualmente, portanto, mesmo que você tenha feito isso SuperClass.method1()chamando super, isso não influencia nenhuma outra chamada de método que possa ser feita no futuro.

Isso significa que não há nenhuma maneira direta para chamar SuperClass.method2()de SuperClass.method1()sem ir embora SubClass.method2(), a menos que você está trabalhando com uma instância real de SuperClass.

Você nem consegue obter o efeito desejado usando o Reflection (consulte a documentação dejava.lang.reflect.Method.invoke(Object, Object...) ).

[EDIT] Ainda parece haver alguma confusão. Deixe-me tentar uma explicação diferente.

Quando você invoca foo(), você realmente invoca this.foo(). Java simplesmente permite que você omita o this. No exemplo da pergunta, o tipo de thisé SubClass.

Então, quando Java executa o código SuperClass.method1(), ele finalmente chega emthis.method2();

Usar supernão altera a instância apontada por this. Portanto, a chamada vai para, SubClass.method2()pois thisé do tipo SubClass.

Talvez seja mais fácil entender quando você imagina que Java passa thiscomo um primeiro parâmetro oculto:

public class SuperClass
{
    public void method1(SuperClass this)
    {
        System.out.println("superclass method1");
        this.method2(this); // <--- this == mSubClass
    }

    public void method2(SuperClass this)
    {
        System.out.println("superclass method2");
    }

}

public class SubClass extends SuperClass
{
    @Override
    public void method1(SubClass this)
    {
        System.out.println("subclass method1");
        super.method1(this);
    }

    @Override
    public void method2(SubClass this)
    {
        System.out.println("subclass method2");
    }
}



public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1(mSubClass);
    }
}

Se você seguir a pilha de chamadas, verá que thisnunca muda, é sempre a instância criada em main().

Aaron Digulla
fonte
Alguém poderia carregar um diagrama deste (trocadilho intencional) passando pela pilha? desde já, obrigado!
laycat de
2
@laycat: Não há necessidade de diagrama. Lembre-se de que o Java não tem "memória" para super. Cada vez que ele chama um método, ele examina o tipo de instância e começa a pesquisar o método com este tipo, não importa quantas vezes você o chame super. Portanto, quando você chama method2uma instância de SubClass, ela sempre verá a SubClassprimeira.
Aaron Digulla de
@AaronDigulla, Você pode explicar mais sobre o "Java não tem memória para super"?
MengT,
@ Truman'sworld: como disse na minha resposta: o uso supernão altera a instância. Ele não define algum campo oculto "de agora em diante, todas as chamadas de método devem começar a usar SuperClass". Ou, dito de outra forma: o valor de thisnão muda.
Aaron Digulla,
@AaronDigulla, então isso significa que a palavra-chave super realmente invoca os métodos herdados na subclasse em vez de ir para a superclasse?
MengT,
15

Você só pode acessar métodos substituídos nos métodos de substituição (ou em outros métodos da classe de substituição).

Portanto: não substitua method2()ou chame super.method2()dentro da versão substituída.

Sean Patrick Floyd
fonte
8

Você está usando a thispalavra - chave que na verdade se refere à "instância atualmente em execução do objeto que você está usando", ou seja, você está invocando this.method2();em sua superclasse, ou seja, ele chamará o método2 () no objeto que você ' está usando, que é a SubClass.

Jose Diaz
fonte
8
verdadeiro, e não usar thistambém não ajudará. Uma invocação não qualificada usa implicitamentethis
Sean Patrick Floyd
3
Por que isso está sendo votado a favor? Esta não é a resposta para esta pergunta. Quando você escrever, method2()o compilador verá this.method2(). Portanto, mesmo que você remova o, thisele não funcionará. O que @Sean Patrick Floyd está dizendo está correto
Shervin Asgari
4
@Shervin ele não está dizendo nada errado, ele apenas não está deixando claro o que acontece se você deixar de forathis
Sean Patrick Floyd
4
A resposta está certa em apontar que thisse refere à "classe de instância de execução concreta" (conhecida em tempo de execução) e não (como o autor da postagem parece acreditar) à "classe de unidade de compilação atual" (onde a palavra-chave é usada, conhecida em tempo de compilação). Mas também pode ser enganoso (como Shervin aponta): thistambém é referenciado implicitamente com a chamada de método simples; method2();é o mesmo quethis.method2();
leonbloy
7

Eu penso assim

+----------------+
|     super      |
+----------------+ <-----------------+
| +------------+ |                   |
| |    this    | | <-+               |
| +------------+ |   |               |
| | @method1() | |   |               |
| | @method2() | |   |               |
| +------------+ |   |               |
|    method4()   |   |               |
|    method5()   |   |               |
+----------------+   |               |
    We instantiate that class, not that one!

Deixe-me mover essa subclasse um pouco para a esquerda para revelar o que está por baixo ... (Cara, eu adoro gráficos ASCII)

We are here
        |
       /  +----------------+
      |   |     super      |
      v   +----------------+
+------------+             |
|    this    |             |
+------------+             |
| @method1() | method1()   |
| @method2() | method2()   |
+------------+ method3()   |
          |    method4()   |
          |    method5()   |
          +----------------+

Then we call the method
over here...
      |               +----------------+
 _____/               |     super      |
/                     +----------------+
|   +------------+    |    bar()       |
|   |    this    |    |    foo()       |
|   +------------+    |    method0()   |
+-> | @method1() |--->|    method1()   | <------------------------------+
    | @method2() | ^  |    method2()   |                                |
    +------------+ |  |    method3()   |                                |
                   |  |    method4()   |                                |
                   |  |    method5()   |                                |
                   |  +----------------+                                |
                   \______________________________________              |
                                                          \             |
                                                          |             |
...which calls super, thus calling the super's method1() here, so that that
method (the overidden one) is executed instead[of the overriding one].

Keep in mind that, in the inheritance hierarchy, since the instantiated
class is the sub one, for methods called via super.something() everything
is the same except for one thing (two, actually): "this" means "the only
this we have" (a pointer to the class we have instantiated, the
subclass), even when java syntax allows us to omit "this" (most of the
time); "super", though, is polymorphism-aware and always refers to the
superclass of the class (instantiated or not) that we're actually
executing code from ("this" is about objects [and can't be used in a
static context], super is about classes).

Em outras palavras, citando a Especificação da linguagem Java :

O formulário super.Identifierse refere ao campo nomeado Identifierdo objeto atual, mas com o objeto atual visto como uma instância da superclasse da classe atual.

O formulário T.super.Identifierse refere ao campo nomeado Identifierda instância envolvente lexicamente correspondente a T, mas com essa instância vista como uma instância da superclasse de T.

Em termos leigos, thisé basicamente um objeto (* o ** objeto; o mesmo objeto que você pode mover em variáveis), a instância da classe instanciada, uma variável simples no domínio de dados; superé como um ponteiro para um bloco de código emprestado que você deseja que seja executado, mais como uma mera chamada de função e é relativo à classe onde é chamado.

Portanto, se você usar superda superclasse, você obterá o código da classe superduper [o avô] executado), enquanto se você usar this(ou se for usado implicitamente) de uma superclasse, ele continuará apontando para a subclasse (porque ninguém a alterou - e ninguém poderia).

Unai Vivi
fonte
2

Se você não quiser que superClass.method1 chame subClass.method2, torne o método2 privado para que ele não possa ser substituído.

Aqui está uma sugestão:

public class SuperClass {

  public void method1() {
    System.out.println("superclass method1");
    this.internalMethod2();
  }

  public void method2()  {
    // this method can be overridden.  
    // It can still be invoked by a childclass using super
    internalMethod2();
  }

  private void internalMethod2()  {
    // this one cannot.  Call this one if you want to be sure to use
    // this implementation.
    System.out.println("superclass method2");
  }

}

public class SubClass extends SuperClass {

  @Override
  public void method1() {
    System.out.println("subclass method1");
    super.method1();
  }

  @Override
  public void method2() {
    System.out.println("subclass method2");
  }
}

Se não funcionasse assim, o polimorfismo seria impossível (ou pelo menos nem metade da utilidade).

Joeri Hendrickx
fonte
2

Visto que a única maneira de evitar que um método seja sobrescrito é usar a palavra - chave super , pensei em mover o método2 () de SuperClass para outra nova classe Base e, em seguida, chamá-lo de SuperClass :

class Base 
{
    public void method2()
    {
        System.out.println("superclass method2");
    }
}

class SuperClass extends Base
{
    public void method1()
    {
        System.out.println("superclass method1");
        super.method2();
    }
}

class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

public class Demo 
{
    public static void main(String[] args) 
    {
        SubClass mSubClass = new SubClass();
        mSubClass.method1();
    }
}

Resultado:

subclass method1
superclass method1
superclass method2
carlos_lm
fonte
2

this sempre se refere ao objeto atualmente em execução.

Para ilustrar melhor o ponto aqui está um esboço simples:

+----------------+
|  Subclass      |
|----------------|
|  @method1()    |
|  @method2()    |
|                |
| +------------+ |
| | Superclass | |
| |------------| |
| | method1()  | |
| | method2()  | |
| +------------+ |
+----------------+

Se você tem uma instância da caixa externa, um Subclassobjeto, onde quer que você se aventure dentro da caixa, mesmo na Superclass"área", ainda é a instância da caixa externa.

Além do mais, neste programa há apenas um objeto que é criado a partir das três classes, portanto, thissó pode se referir a uma coisa e é:

insira a descrição da imagem aqui

conforme mostrado no 'Heap Walker' do Netbeans .

Johnny Baloney
fonte
2
class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        SuperClass se=new SuperClass();
        se.method2();
    }

    public void method2()
    {
        System.out.println("superclass method2");
    }
}


class SubClass extends SuperClass
{
    @Override
    public void method1()
    {
        System.out.println("subclass method1");
        super.method1();
    }

    @Override
    public void method2()
    {
        System.out.println("subclass method2");
    }
}

chamando

SubClass mSubClass = new SubClass();
mSubClass.method1();

saídas

subclasse método1
superclasse método1
superclasse método2

Vijay
fonte
1

Não acredito que você possa fazer isso diretamente. Uma solução alternativa seria ter uma implementação interna privada do método2 na superclasse e chamá-la. Por exemplo:

public class SuperClass
{
    public void method1()
    {
        System.out.println("superclass method1");
        this.internalMethod2();
    }

    public void method2()
    {
        this.internalMethod2(); 
    }
    private void internalMethod2()
    {
        System.out.println("superclass method2");
    }

}
David Gelhar
fonte
1

"this" palavra-chave refere-se à referência de classe atual. Isso significa que, quando usada dentro do método, a classe 'atual' ainda é SubClasse e, portanto, a resposta é explicada.

Ozil
fonte
1

Para resumir, isso aponta para o objeto atual e a invocação do método em java é polimórfica por natureza. Portanto, a seleção do método para execução depende totalmente do objeto apontado por este. Portanto, invocar o método method2 () da classe pai invoca o método2 () da classe filha, pois this aponta para o objeto da classe filha. A definição disso não muda, independentemente da classe usada.

PS. ao contrário dos métodos, as variáveis ​​de membro da classe não são polimórficas.

Mangu Singh Rajpurohit
fonte
0

Durante minha pesquisa para um caso semelhante, acabei verificando o rastreamento da pilha no método da subclasse para descobrir de onde vem a chamada. Provavelmente existem maneiras mais inteligentes de fazer isso, mas funciona para mim e é uma abordagem dinâmica.

public void method2(){
        Exception ex=new Exception();
        StackTraceElement[] ste=ex.getStackTrace();
        if(ste[1].getClassName().equals(this.getClass().getSuperclass().getName())){
            super.method2();
        }
        else{
            //subclass method2 code
        }
}

Acho razoável a questão de ter uma solução para o caso. É claro que existem maneiras de resolver o problema com nomes de métodos diferentes ou até mesmo tipos de parâmetros diferentes, como já mencionado no segmento, mas no meu caso não gosto de confundir com nomes de métodos diferentes.

Beat Siegrist
fonte
Este código é perigoso, arriscado e caro. A criação de exceções exige que a VM construa um stacktrace completo, comparando apenas o nome, e não a assinatura completa, está sujeito a erros. Além disso, cheira a uma grande falha de design.
M. le Rutte
Do ponto de vista do desempenho, meu código não parece produzir mais impacto do que 'new HashMap (). Size ()'. No entanto, posso ter esquecido as preocupações em que você está pensando e não sou um especialista em VM. Eu vejo sua dúvida ao comparar os nomes das classes, mas inclui o pacote que me dá certeza, é minha classe pai. De qualquer forma, gosto da ideia de comparar a assinatura em vez disso, como você faria isso? Em geral, se você tiver uma maneira mais fácil de determinar se o chamador é a superclasse ou qualquer outra pessoa, eu gostaria de saber aqui sobre.
Beat Siegrist em
Se você precisar determinar se o chamador é uma superclasse, eu pensaria seriamente mais se um novo design estivesse em vigor. Este é um antipadrão.
M. le Rutte
Entendo o ponto, mas o pedido geral do segmento é razoável. Em algumas situações, pode fazer sentido que uma chamada de método da superclasse permaneça no contexto da superclasse com qualquer chamada de método aninhada. No entanto, parece não haver maneira de direcionar a chamada de método de acordo na Superclasse.
Venceu Siegrist em
0

Mais estendida a saída da questão levantada, isso dará mais informações sobre o especificador de acesso e o comportamento de substituição.

            package overridefunction;
            public class SuperClass 
                {
                public void method1()
                {
                    System.out.println("superclass method1");
                    this.method2();
                    this.method3();
                    this.method4();
                    this.method5();
                }
                public void method2()
                {
                    System.out.println("superclass method2");
                }
                private void method3()
                {
                    System.out.println("superclass method3");
                }
                protected void method4()
                {
                    System.out.println("superclass method4");
                }
                void method5()
                {
                    System.out.println("superclass method5");
                }
            }

            package overridefunction;
            public class SubClass extends SuperClass
            {
                @Override
                public void method1()
                {
                    System.out.println("subclass method1");
                    super.method1();
                }
                @Override
                public void method2()
                {
                    System.out.println("subclass method2");
                }
                // @Override
                private void method3()
                {
                    System.out.println("subclass method3");
                }
                @Override
                protected void method4()
                {
                    System.out.println("subclass method4");
                }
                @Override
                void method5()
                {
                    System.out.println("subclass method5");
                }
            }

            package overridefunction;
            public class Demo 
            {
                public static void main(String[] args) 
                {
                    SubClass mSubClass = new SubClass();
                    mSubClass.method1();
                }
            }

            subclass method1
            superclass method1
            subclass method2
            superclass method3
            subclass method4
            subclass method5
Sudhirkondle
fonte