Herança: o código da superclasse é virtualmente * copiado * para a subclasse ou é * referido pela subclasse *?

10

Classe Subé uma subclasse de classe Sup. O que isso significa praticamente? Ou, em outras palavras, qual é o significado prático de "herança"?

Opção 1: o código de Sup é virtualmente copiado para Sub. (como em 'copiar e colar', mas sem o código copiado visualmente visto na subclasse).

Exemplo: methodA()é um método originalmente em Sup. Sub estende Sup, assim methodA()como (virtualmente) é copiado e colado para Sub. Agora Sub tem um método chamado methodA(). É idêntico ao Sup methodA()em todas as linhas de código, mas pertence inteiramente ao Sub - e não depende do Sup ou está relacionado ao Sup de qualquer forma.

Opção 2: o código de Sup não é realmente copiado para Sub. Ainda está apenas na superclasse. Mas esse código pode ser acessado através da subclasse e pode ser usado pela subclasse.

Exemplo: methodA()é um método em Sup. Sub estende Sup, então agora methodA()pode ser acessado através Sub assim: subInstance.methodA(). Mas isso realmente invocará methodA()na superclasse. O que significa que methodA () operará no contexto da superclasse, mesmo que tenha sido chamado pela subclasse.

Pergunta: Qual das duas opções é realmente como as coisas funcionam? Se nenhum deles for, descreva como essas coisas realmente funcionam.

Aviv Cohn
fonte
É fácil testar por si mesmo - escreva o código, examine os arquivos de classe (até uma soma de verificação faria isso), modifique a superclasse, compile novamente, verifique os arquivos de classe novamente. Você também pode achar útil ler o Capítulo 3. Compilando para a Java Virtual Machine da especificação da JVM (útil na seção 3.7).
@ MichaelT " virtualmente copiado" é a palavra-chave. Além disso, mesmo se o código estivesse literalmente sendo copiado, isso só poderia acontecer após o carregamento da classe.
@delnan, seria curioso se o Hotspot (ou outros otimizadores de tempo de execução) incorporaria o código em algum momento, mas isso se torna um detalhe de implementação da JVM que pode diferir de uma JVM para outra e, portanto, não pode ser respondida corretamente. O melhor que pode ser feito é olhar para o bytecode compilado (eo invokespecial uso de código de operação que descreve o que realmente acontece)

Respostas:

13

Opção 2.

O bytecode é referenciado dinamicamente no tempo de execução: é por isso que, por exemplo, ocorrem LinkageErrors .

Por exemplo, suponha que você compile duas classes:

public class Parent {
  public void doSomething(String x) { ... }
}

public class Child extends Parent {
  @Override
  public void doSomething(String x) {
    super.doSomething(x);
    ...
  }
}

Agora modifique e recompile a classe pai sem modificar ou recompilar a classe filho :

public class Parent {
  public void doSomething(Collection<?> x) { ... }
}

Por fim, execute um programa que use a classe filho. Você receberá um NoSuchMethodError :

Lançado se um aplicativo tentar chamar um método especificado de uma classe (estática ou instância) e essa classe não tiver mais uma definição desse método.

Normalmente, esse erro é detectado pelo compilador; esse erro pode ocorrer apenas em tempo de execução se a definição de uma classe tiver sido alterada incompativelmente.


fonte
7

Vamos começar com duas classes simples:

package com.michaelt.so.supers;

public class Sup {
    int methodA(int a, int b) {
        return a + b;
    }
}

e depois

package com.michaelt.so.supers;

public class Sub extends Sup {
    @Override
    int methodA(int a, int b) {
        return super.methodA(a, b);
    }
}

Compilando methodA e observando o código de bytes que se obtém:

  methodA(II)I
   L0
    LINENUMBER 6 L0
    ALOAD 0
    ILOAD 1
    ILOAD 2
    INVOKESPECIAL com/michaelt/so/supers/Sup.methodA (II)I
    IRETURN
   L1
    LOCALVARIABLE this Lcom/michaelt/so/supers/Sub; L0 L1 0
    LOCALVARIABLE a I L0 L1 1
    LOCALVARIABLE b I L0 L1 2
    MAXSTACK = 3
    MAXLOCALS = 3

E você pode ver ali, com o método invokespecial, que faz a pesquisa no método A da classe Sup ().

O opcode invokepecial tem a seguinte lógica:

  • Se C contiver uma declaração para um método de instância com o mesmo nome e descritor que o método resolvido, esse método será chamado. O procedimento de pesquisa termina.
  • Caso contrário, se C tiver uma superclasse, esse mesmo procedimento de pesquisa será executado recursivamente usando a superclasse direta de C. O método a ser invocado é o resultado da invocação recursiva desse procedimento de pesquisa.
  • Caso contrário, um AbstractMethodError é gerado.

Nesse caso, não há método de instância com o mesmo nome e descritor em sua classe, portanto o primeiro marcador não será acionado. A segunda bala, no entanto - haverá uma superclasse e ela invocará o método A da super.

O compilador não alinha isso e não há cópia da fonte de Sup na classe.

No entanto, a história ainda não está concluída. Este é apenas ocódigo compilado . Quando o código atinge a JVM, o HotSpot pode se envolver.

Infelizmente, eu não sei muito sobre isso, então apelo à autoridade sobre este assunto e vou para Inlining em Java, onde é dito que o HotSpot pode incorporar métodos (mesmo métodos não finais).

Indo para os documentos , observe que, se uma chamada de método específica se tornar um hot spot, em vez de fazer essa pesquisa a cada vez, essas informações poderão ser incorporadas - copiando efetivamente o código do método Sup (A) para Sub methodA ().

Isso é feito em tempo de execução, na memória, com base em como o aplicativo está se comportando e quais otimizações são necessárias para acelerar o desempenho.

Conforme declarado no HotSpot Internals for OpenJDK, "Métodos são frequentemente incorporados. Invocações estáticas, privadas, finais e / ou" especiais "são fáceis de incorporar".

Se você pesquisar nas opções da JVM , encontrará uma opção de -XX:MaxInlineSize=35(35 sendo o padrão), que é o número máximo de bytes que podem ser incorporados. Vou salientar que é por isso que o Java gosta de ter muitos métodos pequenos - porque eles podem ser facilmente incorporados. Esses pequenos métodos se tornam mais rápidos quando são chamados mais porque podem ser incorporados. E, embora se possa brincar com esse número e aumentá-lo, isso pode fazer com que outras otimizações sejam menos eficazes. (questão SO relacionada: estratégia de inline do HotSpot JIT, que aponta várias outras opções para espiar os aspectos internos do inline que o HotSpot está fazendo).

Portanto, não - o código não está embutido no momento da compilação. E sim - o código pode muito bem ser incorporado em tempo de execução se as otimizações de desempenho o justificarem.

E tudo o que escrevi sobre o HotSpot embutido se aplica apenas ao HotSpot JVM distribuído pela Oracle. Se você olhar a lista de máquinas virtuais Java da wikipedia, existem muito mais do que apenas HotSpot e a maneira como essas JVMs lidam com inlining pode ser completamente diferente do que eu descrevi acima. Apache Harmony, Dalvik, ART - as coisas podem funcionar de maneira diferente lá.

Comunidade
fonte
0

o código não é copiado, é acessado por referência:

  • a subclasse faz referência a seus métodos e a superclasse
  • a superclasse faz referência a seus métodos

compiladores podem otimizar como isso é representado / executado na memória, mas essa é basicamente a estrutura

Steven A. Lowe
fonte