Implementación de la llamada al método.

El código de la aplicación ingresa al área de datos de tiempo de ejecución de la JVM a través de varias etapas de compilación y carga de clases. La ejecución del código es en realidad la ejecución del método en esencia. Desde la perspectiva de la JVM, sigue siendo la ejecución del código de bytes en el análisis final.


1. Análisis

En cuanto a las llamadas a métodos, el código de bytes de Java proporciona un total de 5 instrucciones para llamar a diferentes tipos de métodos:

  1. invokestatic: usado para llamar a métodos estáticos
  2. invokespecial: se utiliza para llamar a métodos privados, constructores y superpalabras clave, etc.
  3. invokevirtual: se utiliza para llamar a todos los métodos virtuales; como los métodos de instancia no privados públicos y protegidos, la mayoría de las llamadas a métodos pertenecen a este tipo
  4. invokeinterface: método de interfaz de llamada
  5. invokedynamic: llama a un método dinámico

1.1, método no virtual

Si la versión de llamada específica del método se determina en el momento de la compilación y esta versión es inmutable en el tiempo de ejecución, dicho método se denomina método no virtual .


Siempre que la instrucción pueda llamar invokestatical invokespecialmétodo, la versión de llamada única se puede determinar en la fase de análisis. Los métodos que cumplen esta condición incluyen métodos estáticos, métodos privados, constructores de instancias y métodos de clase principal, además de los modificados por la instrucción final. método (aunque se invoca mediante la instrucción invokevirtual), estas cinco llamadas a métodos resolverán la referencia simbólica a una referencia directa al método cuando se carga la clase. No es necesario completarlo en tiempo de ejecución.


1.2 Método virtual

A diferencia de los métodos no virtuales, los métodos que no lo son son virtuales . Incluye principalmente dos categorías en los siguientes códigos de bytes:

  • invokevirtualSe utiliza para llamar a métodos de instancia no privados, como públicos y protegidos. La mayoría de las llamadas a métodos pertenecen a este tipo (excluidos los métodos modificados por final)
  • invokeinterfaceSimilar a la instrucción anterior, pero actúa sobre la clase de interfaz.

¿Por qué se llama método virtual? Es decir, el método es mutable en tiempo de ejecución.

En muchos casos, la JVM necesita determinar el método de destino de la llamada de acuerdo con el tipo dinámico de la persona que llama, este es el proceso de enlace dinámico, por el contrario, la instrucción invokestatic más la instrucción invokespecial pertenecen al proceso de enlace estático.


2. Distribución

Java es un lenguaje de programación orientado a objetos porqueJava tiene tres características básicas de la orientación a objetos: herencia, encapsulación y polimorfismo.

El proceso de llamada de despacho revelará algunas de las manifestaciones más básicas de las características polimórficas, como cómo se implementan la "sobrecarga" y la "anulación" en la máquina virtual Java.


2.1, asignación estática

Es más común en la sobrecarga de métodos, de la siguiente manera:

public class StaticDispatch {
    
    

    static abstract class Human {
    
    }

    static class Man extends Human {
    
    }

    static class Woman extends Human {
    
    }

    public void sayHello(Human guy) {
    
    
        System.out.println("Hello, Guy");
    }

    public void sayHello(Man guy) {
    
    
        System.out.println("Hello, Gentleman");
    }

    public void sayHello(Woman guy) {
    
    
        System.out.println("Hello, Lady");
    }

    public static void main(String[] args) {
    
    
        Human h1 = new Man();
        Human h2 = new Woman();

        StaticDispatch sr = new StaticDispatch();
        sr.sayHello(h1);
        sr.sayHello(h2);
    }
}

inserte la descripción de la imagen aquí

"Humano" se denomina tipo estático de variable (tipo estático), o tipo de apariencia (tipo aparente), y el siguiente "hombre" se denomina tipo real de variable (tipo real).

Tanto el tipo estático como el tipo real pueden sufrir algunos cambios en el programa, la diferencia es que el cambio del tipo estático solo ocurre cuando se usa, el tipo estático de la variable en sí no se cambiará y el tipo estático final se conoce en tiempo de compilación, mientras que el resultado real del cambio de tipo solo se puede determinar en tiempo de ejecución, y el compilador no sabe cuál es el tipo real de un objeto al compilar el programa.


En el código se definen dos variables con el mismo tipo estático pero diferentes tipos reales, pero la máquina virtual (compilador para ser precisos) utiliza el tipo estático del parámetro en lugar del tipo real como base para juzgar durante la sobrecarga. Y el tipo estático se conoce en el momento de la compilación, por lo que en la etapa de compilación, el compilador Javac decidirá qué versión sobrecargada usar de acuerdo con el tipo estático del parámetro, por lo que se selecciona como el destino de llamada sayHello(Human). Todas las acciones de envío que dependen de tipos estáticos para localizar implementaciones de métodos se denominan envío estático.

Una aplicación típica del envío estático es la sobrecarga de métodos.El envío estático ocurre en tiempo de compilación, determinando así que la máquina virtual no realiza realmente la acción enviada estáticamente.


2.2, asignación dinámica

Es más común en la reescritura de métodos, de la siguiente manera:

public class DynamicDispatch {
    
    
    static abstract class Human {
    
    
        protected abstract void sayHello();
    }

    static class Man extends Human {
    
    
        @Override
        protected void sayHello() {
    
    
            System.out.println("Hello, Gentleman");
        }
    }

    static class Woman extends Human {
    
    
        @Override
        protected void sayHello() {
    
    
            System.out.println("Hello, Lady");
        }
    }

    public static void main(String[] args) {
    
    
        Human h1 = new Man();
        Human h2 = new Woman();
        h1.sayHello();
        h2.sayHello();
    }
}

inserte la descripción de la imagen aquí

Las dos variables, hombre y mujer, también de tipo estático Humano, sayHello()realizan comportamientos diferentes cuando se llama al método, y realizan métodos diferentes en las dos llamadas. La razón obvia de esto es que los tipos reales de las dos variables son diferentes.


En términos de implementación, el método más común es crear una tabla de métodos virtuales en el área de métodos de la clase . La dirección de entrada real de cada método se almacena en la tabla de métodos virtuales. Si un método no se anula en la subclase, la entrada de dirección en la tabla de métodos virtuales de la subclase es consistente con la entrada de dirección del mismo método en la clase principal, y ambas apuntan a la entrada de implementación de la clase principal. Si este método se anula en la subclase, la dirección en la tabla de métodos de la subclase se reemplazará con la dirección de entrada que apunta a la versión de implementación de la subclase.
inserte la descripción de la imagen aquí
Como se muestra en la figura, Son reescribe el método del Padre, por lo que la tabla de métodos de Son no tiene una flecha que apunte a los datos de tipo Padre. Pero ni el Hijo ni el Padre reescriben métodos de Objeto, por lo que todos los métodos heredados de Objeto en sus tablas de métodos apuntan al tipo de datos de Objeto.


3. expresión lambda

invokedynamicPor lo general, aparece en la sintaxis Lambda y invokedynamicla capa inferior de la instrucción se MethodHandleimplementa mediante un identificador de método.


Un identificador de método es una referencia ejecutable a métodos estáticos y de instancia, así como a métodos ficticios get y set. En pocas palabras, se puede llamar al método correspondiente a través del identificador del método.

3.1, identificador del método MethodHandle

El proceso de llamar a un método con MethodHandle es:

  1. Cree un MethodType para obtener la firma del método especificado (parámetros de entrada y salida)
  2. Encuentre el identificador del método MethodHandle de MethodType en Lookup
  3. Los parámetros del método entrante llaman al método a través de MethodHandle
public class MethodHandleDemo {
    
    
    static class Dog {
    
    
        protected String sayHello() {
    
    
            return "Woof Woof...";
        }
    }

    static class Human {
    
    
        protected String sayHello() {
    
    
            return "Hello, Guy";
        }
    }

    static class Man extends Human {
    
    
        @Override
        protected String sayHello() {
    
    
            return "Hello, Gentleman";
        }
    }

    static String sayHello(Object obj) throws Throwable {
    
    
        // 从工厂方法中获取方法句柄
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        // 创建MethodType,获取指定方法的签名(出参和入参)
        MethodType methodType = MethodType.methodType(String.class);
        // 获取具体的MethodHandle
        MethodHandle methodHandle = lookup.findVirtual(obj.getClass(), "sayHello", methodType);
        
        return (String) methodHandle.invoke(obj);
    }

    public static void main(String[] args) throws Throwable {
    
    
        System.out.println(MethodHandleDemo.sayHello(new Dog()));
        System.out.println(MethodHandleDemo.sayHello(new Human()));
        System.out.println(MethodHandleDemo.sayHello(new Man()));
    }
}

inserte la descripción de la imagen aquí
Lambda en realidad se realiza a través del identificador del método mencionado anteriormente y compilará un conjunto de códigos de bytes que pueden llamar a MethodHandle de acuerdo con el código de la expresión Lambda que escribió.

Supongo que te gusta

Origin blog.csdn.net/rockvine/article/details/124840072
Recomendado
Clasificación