Static vs. Vinculação dinâmica em Java


103

No momento, estou fazendo uma atribuição para uma de minhas classes e, nela, tenho que dar exemplos, usando a sintaxe Java, de vinculação estática e dinâmica .

Eu entendo o conceito básico, que a vinculação estática acontece em tempo de compilação e a vinculação dinâmica acontece em tempo de execução, mas não consigo descobrir como elas realmente funcionam especificamente.

Encontrei um exemplo de vinculação estática online que dá este exemplo:

public static void callEat(Animal animal) {
    System.out.println("Animal is eating");
}

public static void callEat(Dog dog) {
    System.out.println("Dog is eating");
}

public static void main(String args[])
{
    Animal a = new Dog();
    callEat(a);
}

E que isso imprimiria "animal está comendo" porque a chamada para callEatusa vinculação estática , mas não tenho certeza de por que isso é considerado vinculação estática.

Até agora, nenhuma das fontes que vi conseguiu explicar isso de uma forma que eu possa acompanhar.



1
Observe que existem vários conceitos diferentes que são chamados de "vinculação". Neste caso particular, para este tipo de ligação (que envolve uma escolha entre "assinaturas" de método semelhantes, mas não idênticas), o compilador (que toma decisões "estáticas", uma vez que não variam em tempo de execução) decide que o parâmetro é um "Animal" porque esse é o tipo (estático) da variável "a".
Hot Licks

(Há idiomas em que a escolha da assinatura do método específico seria deixada até o tempo de execução e callEat (Dog) seria selecionado.)
Hot Licks

Respostas:


114

Da postagem do blog revisitada por Javar :

Aqui estão algumas diferenças importantes entre vinculação estática e dinâmica:

  1. A ligação estática em Java ocorre durante o tempo de compilação, enquanto a ligação dinâmica ocorre durante o tempo de execução.
  2. private, finale staticmétodos e variáveis ​​usam vinculação estática e são vinculados pelo compilador enquanto métodos virtuais são vinculados durante o tempo de execução com base no objeto de tempo de execução.
  3. A ligação estática usa Type( classem Java) informações para ligação, enquanto a ligação dinâmica usa o objeto para resolver a ligação.
  4. Métodos sobrecarregados são vinculados por meio de vinculação estática, enquanto métodos substituídos são vinculados por vinculação dinâmica em tempo de execução.

Aqui está um exemplo que o ajudará a entender a vinculação estática e dinâmica em Java.

Exemplo de ligação estática em Java

public class StaticBindingTest {  
    public static void main(String args[]) {
        Collection c = new HashSet();
        StaticBindingTest et = new StaticBindingTest();
        et.sort(c);
    }
    //overloaded method takes Collection argument
    public Collection sort(Collection c) {
        System.out.println("Inside Collection sort method");
        return c;
    }
    //another overloaded method which takes HashSet argument which is sub class
    public Collection sort(HashSet hs) {
        System.out.println("Inside HashSet sort method");
        return hs;
    }
}

Resultado : método de classificação Inside Collection

Exemplo de vinculação dinâmica em Java

public class DynamicBindingTest {   
    public static void main(String args[]) {
        Vehicle vehicle = new Car(); //here Type is vehicle but object will be Car
        vehicle.start(); //Car's start called because start() is overridden method
    }
}

class Vehicle {
    public void start() {
        System.out.println("Inside start method of Vehicle");
    }
}

class Car extends Vehicle {
    @Override
    public void start() {
        System.out.println("Inside start method of Car");
    }
}

Resultado: Método de partida interno do carro



11
Ainda não entendo a diferença,
technazi

9
A vinculação estática @technazi apenas olha para o tipo (o que quer que esteja antes do igual, por exemplo, Collection c = new HashSet (); portanto, será visto apenas como um objeto de coleção quando, na verdade, for um hashset). A vinculação dinâmica leva em consideração o objeto real (o que vem depois do igual para que ele realmente reconheça um HashSet).
Mark

22

Conectar uma chamada de método ao corpo do método é conhecido como Binding. Como disse Maulik, "A vinculação estática usa informações de tipo (classe em Java) para vinculação, enquanto a vinculação dinâmica usa Object para resolver a vinculação." Portanto, este código:

public class Animal {
    void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {
        Animal a = new Dog();
        a.eat(); // prints >> dog is eating...
    }

    @Override
    void eat() {
        System.out.println("dog is eating...");
    }
}

Irá produzir o resultado: o cão está comendo ... porque está usando a referência do objeto para descobrir qual método usar. Se mudarmos o código acima para este:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

Ele produzirá: animal está comendo ... porque é um método estático, então está usando Type (neste caso Animal) para resolver qual método estático chamar. Ao lado dos métodos estáticos, os métodos private e final usam a mesma abordagem.


1
Por que o Java não pode deduzir que aé realmente um Dogtempo de compilação?
Minh Nghĩa

4

O compilador sabe apenas que o tipo de "a" é Animal; isso acontece em tempo de compilação, por isso é chamado de vinculação estática (sobrecarga de método). Mas se for uma vinculação dinâmica, ele chamaria o Dogmétodo de classe. Aqui está um exemplo de vinculação dinâmica.

public class DynamicBindingTest {

    public static void main(String args[]) {
        Animal a= new Dog(); //here Type is Animal but object will be Dog
        a.eat();       //Dog's eat called because eat() is overridden method
    }
}

class Animal {

    public void eat() {
        System.out.println("Inside eat method of Animal");
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("Inside eat method of Dog");
    }
}

Resultado: Método de alimentação interna do cão


Isso não geraria um erro de compilação, como "Não é possível fazer referência a uma classe / método não estático em um contexto estático"? Sempre fico confuso com isso, tendo em vista que main é estático. Desde já, obrigado.
Amnor

3

Bem, para entender como a vinculação estática e dinâmica realmente funciona? ou como eles são identificados pelo compilador e JVM?

Vamos pegar o exemplo abaixo onde Mammalestá uma classe pai que tem um método speak()e uma Humanclasse extends Mammal, sobrescreve o speak()método e, em seguida, novamente o sobrecarrega com speak(String language).

public class OverridingInternalExample {

    private static class Mammal {
        public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
    }

    private static class Human extends Mammal {

        @Override
        public void speak() { System.out.println("Hello"); }

        // Valid overload of speak
        public void speak(String language) {
            if (language.equals("Hindi")) System.out.println("Namaste");
            else System.out.println("Hello");
        }

        @Override
        public String toString() { return "Human Class"; }

    }

    //  Code below contains the output and bytecode of the method calls
    public static void main(String[] args) {
        Mammal anyMammal = new Mammal();
        anyMammal.speak();  // Output - ohlllalalalalalaoaoaoa
        // 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Mammal humanMammal = new Human();
        humanMammal.speak(); // Output - Hello
        // 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Human human = new Human();
        human.speak(); // Output - Hello
        // 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V

        human.speak("Hindi"); // Output - Namaste
        // 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
    }
}

Quando compilamos o código acima e tentamos olhar para o bytecode usando javap -verbose OverridingInternalExample, podemos ver que o compilador gera uma tabela constante onde atribui códigos inteiros para cada chamada de método e código de byte para o programa que extraí e incluí no próprio programa ( veja os comentários abaixo de cada chamada de método)

Bytecode do programa

Ao olhar para o código acima, podemos ver que o bytecode de humanMammal.speak(), human.speak()e human.speak("Hindi")são totalmente diferentes ( invokevirtual #4, invokevirtual #7, invokevirtual #9) porque o compilador é capaz de diferenciar entre eles com base na lista de argumentos e referência de classe. Como tudo isso é resolvido estaticamente em tempo de compilação, é por isso que a sobrecarga de método é conhecida como polimorfismo estático ou ligação estática .

Mas bytecode para anyMammal.speak()e humanMammal.speak()é o mesmo ( invokevirtual #4) porque de acordo com o compilador ambos os métodos são chamados na Mammalreferência.

Portanto, agora surge a pergunta: se ambas as chamadas de método têm o mesmo bytecode, como a JVM sabe qual método chamar?

Bem, a resposta está escondida no próprio bytecode e é um invokevirtualconjunto de instruções. O JVM usa a invokevirtualinstrução para invocar o equivalente Java dos métodos virtuais C ++. Em C ++, se quisermos sobrescrever um método em outra classe, precisamos declará-lo como virtual, mas em Java, todos os métodos são virtuais por padrão porque podemos sobrescrever todos os métodos na classe filha (exceto métodos privados, finais e estáticos).

Em Java, cada variável de referência contém dois ponteiros ocultos

  1. Um ponteiro para uma tabela que novamente contém métodos do objeto e um ponteiro para o objeto Class. por exemplo, [speak (), speak (String) Class objeto]
  2. Um ponteiro para a memória alocada no heap para os dados desse objeto, por exemplo, valores de variáveis ​​de instância.

Portanto, todas as referências de objeto contêm indiretamente uma referência a uma tabela que contém todas as referências de método desse objeto. Java pegou emprestado esse conceito de C ++ e essa tabela é conhecida como tabela virtual (vtable).

Uma vtable é uma estrutura semelhante a um array que contém nomes de métodos virtuais e suas referências nos índices de array. A JVM cria apenas uma vtable por classe ao carregar a classe na memória.

Portanto, sempre que a JVM encontra um invokevirtualconjunto de instruções, ela verifica a vtable dessa classe para a referência do método e invoca o método específico que, em nosso caso, é o método de um objeto, não a referência.

Como tudo isso é resolvido apenas no tempo de execução e no tempo de execução a JVM consegue saber qual método invocar, é por isso que a Substituição de Método é conhecida como Polimorfismo Dinâmico ou simplesmente Polimorfismo ou Ligação Dinâmica .

Você pode ler mais detalhes no meu artigo Como a JVM trata a sobrecarga e a substituição de métodos internamente .


2

Existem três diferenças principais entre vinculação estática e dinâmica ao projetar os compiladores e como as variáveis e procedimentos são transferidos para o tempo de execução ambiente de . Essas diferenças são as seguintes:

Vinculação estática : na vinculação estática, três problemas a seguir são discutidos:

  • Definição de um procedimento

  • Declaração de um nome (variável, etc.)

  • Âmbito da declaração

Vinculação dinâmica : três problemas encontrados na vinculação dinâmica são os seguintes:

  • Ativação de um procedimento

  • Vinculação de um nome

  • Tempo de vida de uma encadernação


1

Com o método estático nas classes pai e filho: Static Binding

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child(); 
        pc.start(); 
    }
}

class parent {
    static public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

    static public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of parent

Vinculação dinâmica:

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child();
        pc.start(); 
    }
}

class parent {
   public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

   public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of child

0

Todas as respostas aqui estão corretas, mas quero acrescentar algo que está faltando. quando você está substituindo um método estático, parece que o estamos substituindo, mas na verdade não é uma substituição de método. Em vez disso, é chamado de ocultação de método. Os métodos estáticos não podem ser substituídos em Java.

Veja o exemplo abaixo:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

Na vinculação dinâmica, o método é chamado dependendo do tipo de referência e não do tipo de objeto que a variável de referência está mantendo. Aqui, a vinculação estática acontece porque a ocultação do método não é um polimorfismo dinâmico. Se você remover a palavra-chave estática na frente de eat () e torná-la um método não estático, ela mostrará o polimorfismo dinâmico e não ocultação de método.

Encontrei o link abaixo para apoiar minha resposta: https://youtu.be/tNgZpn7AeP0


0

No caso do tipo de ligação estática do objeto determinado no tempo de compilação, enquanto no tipo de ligação dinâmica do objeto é determinado no tempo de execução.



class Dainamic{

    void run2(){
        System.out.println("dainamic_binding");
    }

}


public class StaticDainamicBinding extends Dainamic {

    void run(){
        System.out.println("static_binding");
    }

    @Override
    void run2() {
        super.run2();
    }

    public static void main(String[] args) {
        StaticDainamicBinding st_vs_dai = new StaticDainamicBinding();
        st_vs_dai.run();
        st_vs_dai.run2();
    }

}

-3

Porque o compilador conhece a ligação em tempo de compilação. Se você invocar um método em uma interface, por exemplo, o compilador não pode saber e a ligação é resolvida em tempo de execução porque o objeto real que tem um método invocado pode ser um entre vários. Portanto, isso é tempo de execução ou vinculação dinâmica.

Sua invocação está vinculada à classe Animal no momento da compilação porque você especificou o tipo. Se você passasse essa variável para outro método em outro lugar, ninguém saberia (além de você porque você a escreveu) que classe real seria. A única pista é o tipo declarado de Animal.


1
Não é verdade. O compilador tomaria exatamente a mesma decisão se você estivesse fazendo uma chamada em uma interface.
Hot Licks

@HotLicks Exatamente a mesma decisão de quê? Se você compilar uma classe para invocar um método foo (String str) em uma interface, o compilador não pode saber no momento da compilação em qual classe o método foo (String str) deve ser invocado. Apenas no tempo de execução a invocação do método pode ser vinculada a uma implementação de classe específica.
Aaron

Mas a vinculação estática à assinatura do método específico ainda ocorreria. O compilador ainda escolheria callEat (Animal) em vez de callEat (Dog).
Hot Licks

@HotLicks Claro, mas não foi essa a pergunta que respondi. Talvez tenha sido enganoso da minha parte: DI comparou isso a invocar em uma interface para destacar que, no momento da compilação, o compilador não pode saber se você realmente instanciou uma subclasse / implementação diferente ou não.
Aaron

Na verdade, em tempo de compilação, o compilador pode (neste caso) muito facilmente saber que "a" é um Cachorro. Na verdade, provavelmente é preciso ir longe para "esquecer" isso.
Hot Licks
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.