Estático vs. Enlace dinámico en Java


103

Actualmente estoy haciendo una tarea para una de mis clases, y en ella tengo que dar ejemplos, usando la sintaxis de Java, de enlace estático y dinámico .

Entiendo el concepto básico, que el enlace estático ocurre en tiempo de compilación y el enlace dinámico ocurre en tiempo de ejecución, pero no puedo entender cómo funcionan realmente específicamente.

Encontré un ejemplo de enlace estático en línea que da este ejemplo:

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

Y que esto imprimiría "animal está comiendo" porque la llamada a callEatusa enlace estático , pero no estoy seguro de por qué esto se considera enlace estático.

Hasta ahora, ninguna de las fuentes que he visto ha logrado explicar esto de una manera que pueda seguir.



1
Tenga en cuenta que hay varios conceptos diferentes que se denominan "vinculantes". En este caso particular, para este tipo de enlace (que implica una elección entre "firmas" de método similares pero no idénticas), el compilador (que toma decisiones "estáticas", ya que no varían en tiempo de ejecución) decide que el parámetro es un "Animal" porque ese es el tipo (estático) de la variable "a".
Hot Licks

(Hay idiomas en los que la elección de la firma del método específico se dejaría hasta el tiempo de ejecución y se seleccionaría callEat (Dog)).
Hot Licks

Respuestas:


114

De la publicación de blog visitada por Java :

Aquí hay algunas diferencias importantes entre el enlace estático y dinámico:

  1. El enlace estático en Java se produce durante el tiempo de compilación, mientras que el enlace dinámico se produce durante el tiempo de ejecución.
  2. private, finaly los staticmétodos y variables utilizan enlaces estáticos y están vinculados por el compilador, mientras que los métodos virtuales se vinculan durante el tiempo de ejecución según el objeto de tiempo de ejecución.
  3. El enlace estático utiliza Type( classen Java) información para el enlace, mientras que el enlace dinámico utiliza el objeto para resolver el enlace.
  4. Los métodos sobrecargados se enlazan mediante enlaces estáticos, mientras que los métodos anulados se enlazan mediante enlaces dinámicos en tiempo de ejecución.

Aquí hay un ejemplo que le ayudará a comprender el enlace estático y dinámico en Java.

Ejemplo de enlace estático en 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 clasificación de colección interior

Ejemplo de enlace dinámico en 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");
    }
}

Salida: método de arranque interior del coche



11
Todavía no entiendo la diferencia
technazi

9
El enlace estático de @technazi solo mira el tipo (lo que sea antes de los iguales, por ejemplo, Collection c = new HashSet (); por lo que se verá solo como un objeto de colección cuando de hecho es un hashset). El enlace dinámico tiene en cuenta el objeto real (lo que sigue a los iguales para que realmente reconozca que es un HashSet).
Mark

22

La conexión de una llamada de método al cuerpo del método se conoce como Binding. Como dijo Maulik, "el enlace estático utiliza información de tipo (clase en Java) para el enlace, mientras que el enlace dinámico utiliza Object para resolver el enlace". Entonces 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...");
    }
}

Producirá el resultado: el perro está comiendo ... porque está usando la referencia del objeto para encontrar qué método usar. Si cambiamos el código anterior a esto:

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

Producirá: animal está comiendo ... porque es un método estático, por lo que está usando Type (en este caso Animal) para resolver qué método estático llamar. Además de los métodos estáticos, los métodos privados y finales utilizan el mismo enfoque.


1
¿Por qué Java no puede deducir que en arealidad es un Dogtiempo de compilación?
Minh Nghĩa

4

El compilador solo sabe que el tipo de "a" es Animal; esto sucede en tiempo de compilación, por lo que se denomina enlace estático (sobrecarga de métodos). Pero si es un enlace dinámico, llamaría al Dogmétodo de clase. A continuación, se muestra un ejemplo de enlace dinámico.

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 comer interior de perro


¿No arrojaría un error de compilación como "No se puede hacer referencia a una clase / método no estático desde un contexto estático"? Siempre estoy confundido con eso, teniendo en cuenta que main es estático. Gracias por adelantado.
Amnor

3

Bueno, para entender cómo la unión estática y dinámica funciona realmente el ? o ¿cómo se identifican mediante el compilador y la JVM?

Tomemos el siguiente ejemplo donde Mammalhay una clase padre que tiene un método speak()y la Humanclase se extiende Mammal, anula el speak()método y luego lo sobrecarga de nuevo 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
    }
}

Cuando compilamos el código anterior e intentamos mirar el javap -verbose OverridingInternalExamplecódigo de bytes usando , podemos ver que el compilador genera una tabla constante donde asigna códigos enteros a cada llamada de método y código de bytes para el programa que he extraído e incluido en el programa mismo ( vea los comentarios debajo de cada llamada al método)

Código de byte del programa

Al observar el código de seguridad, podemos ver que el código de bytes de humanMammal.speak(), human.speak()y human.speak("Hindi")son totalmente diferentes ( invokevirtual #4, invokevirtual #7, invokevirtual #9) ya que el compilador es capaz de diferenciar entre ellos sobre la base de la lista de argumentos y la referencia de clase. Debido a que todo esto se resuelve en tiempo de compilación de forma estática, es por eso que la sobrecarga de métodos se conoce como polimorfismo estático o enlace estático .

Pero el código de bytes para anyMammal.speak()y humanMammal.speak()es el mismo ( invokevirtual #4) porque, según el compilador, ambos métodos se llaman por Mammalreferencia.

Entonces, ahora surge la pregunta si ambas llamadas a métodos tienen el mismo código de bytes, ¿cómo sabe JVM a qué método llamar?

Bueno, la respuesta está oculta en el propio código de bytes y es un invokevirtualconjunto de instrucciones. JVM utiliza elinvokevirtual instrucción para invocar el equivalente de Java de los métodos virtuales de C ++. En C ++, si queremos anular un método en otra clase, debemos declararlo como virtual, pero en Java, todos los métodos son virtuales de forma predeterminada porque podemos anular todos los métodos de la clase secundaria (excepto los métodos privados, finales y estáticos).

En Java, cada variable de referencia contiene dos punteros ocultos

  1. Un puntero a una tabla que nuevamente contiene métodos del objeto y un puntero al objeto Class. por ejemplo, [speak (), speak (String) Class object]
  2. Un puntero a la memoria asignada en el montón para los datos de ese objeto, por ejemplo, valores de variables de instancia.

Entonces, todas las referencias a objetos contienen indirectamente una referencia a una tabla que contiene todas las referencias a métodos de ese objeto. Java ha tomado prestado este concepto de C ++ y esta tabla se conoce como tabla virtual (vtable).

Una vtable es una estructura similar a una matriz que contiene nombres de métodos virtuales y sus referencias en índices de matriz. JVM crea solo una tabla virtual por clase cuando carga la clase en la memoria.

Entonces, cada vez que JVM se encuentra con un invokevirtualconjunto de instrucciones, verifica la tabla vtable de esa clase para la referencia del método e invoca el método específico que en nuestro caso es el método de un objeto, no la referencia.

Debido a que todo esto se resuelve solo en tiempo de ejecución y en tiempo de ejecución JVM llega a saber qué método invocar, es por eso que la Anulación de método se conoce como polimorfismo dinámico o simplemente polimorfismo o enlace dinámico .

Puede leer más detalles en mi artículo ¿Cómo maneja la JVM la sobrecarga y anulación de métodos internamente ?


2

Hay tres diferencias principales entre el enlace estático y dinámico al diseñar los compiladores y cómo se transfieren las variables y los procedimientos al entorno de ejecución . Estas diferencias son las siguientes:

Enlace estático : En el enlace estático se discuten los siguientes tres problemas:

  • Definición de un procedimiento

  • Declaración de un nombre (variable, etc.)

  • Alcance de la declaración

Enlace dinámico : los tres problemas que surgen en el enlace dinámico son los siguientes:

  • Activación de un procedimiento

  • Encuadernación de un nombre

  • Vida útil de una encuadernación


1

Con el método estático en la clase principal y secundaria: Enlace estático

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

Encuadernación 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 las respuestas aquí son correctas, pero quiero agregar algo que falta. cuando está anulando un método estático, parece que lo estamos anulando, pero en realidad no es un método anulado. En su lugar, se llama método de ocultación. Los métodos estáticos no se pueden anular en Java.

Mira el siguiente ejemplo:

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

En la vinculación dinámica, se llama al método dependiendo del tipo de referencia y no del tipo de objeto que contiene la variable de referencia. Aquí la vinculación estática sucede porque la ocultación del método no es un polimorfismo dinámico. Si elimina la palabra clave estática delante de eat () y la convierte en un método no estático, le mostrará el polimorfismo dinámico y no la ocultación de métodos.

Encontré el siguiente enlace para respaldar mi respuesta: https://youtu.be/tNgZpn7AeP0


0

En el caso del tipo de enlace estático del objeto determinado en el tiempo de compilación, mientras que en el tipo de enlace dinámico del objeto se determina en el tiempo de ejecución.



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 el compilador conoce el enlace en tiempo de compilación. Si invoca un método en una interfaz, por ejemplo, el compilador no puede saberlo y el enlace se resuelve en tiempo de ejecución porque el objeto real que tiene un método invocado podría ser uno de varios. Por lo tanto, eso es tiempo de ejecución o enlace dinámico.

Su invocación está vinculada a la clase Animal en tiempo de compilación porque ha especificado el tipo. Si pasa esa variable a otro método en otro lugar, nadie sabría (aparte de usted porque lo escribió) qué clase real sería. La única pista es el tipo de animal declarado.


1
No es verdad. El compilador tomaría exactamente la misma decisión si estuviera haciendo una llamada en una interfaz.
Hot Licks

@HotLicks ¿Exactamente la misma decisión que qué? Si compila una clase para invocar un método foo (String str) en una interfaz, el compilador no puede saber en el momento de la compilación en qué clase debe invocarse el método foo (String str). Solo en tiempo de ejecución se puede vincular la invocación del método a una implementación de clase particular.
Aaron

Pero todavía se produciría un enlace estático a la firma del método específico. El compilador aún elegiría callEat (Animal) sobre callEat (Dog).
Hot Licks

@HotLicks Seguro, pero esa no es la pregunta que respondí. Quizás fue engañoso por mi parte: DI lo comparó con invocar en una interfaz para resaltar que en el momento de la compilación el compilador no puede saber si realmente creó una instancia de una subclase / implementación diferente o no.
Aaron

En realidad, en el momento de la compilación, el compilador puede (en este caso) saber muy fácilmente que "a" es un perro. De hecho, es probable que tenga que hacer todo lo posible para "olvidarlo".
Hot Licks
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.