Capítulo 10_Compilación y optimización de interfaces

Visión general

El período de compilación puede referirse a lo siguiente:

  1. Compilador front-end: el proceso de convertir archivos java en archivos de clase, como Javac
  2. Compilador Just-In-Time (compilador JIT): el proceso de convertir el código de bytes en código de máquina nativo en tiempo de ejecución, como los compiladores C1 y C2 de la máquina virtual del hotspot.
  3. Precompilador estático: el proceso de compilar directamente el programa en código binario relacionado con el conjunto de instrucciones de destino

En Java, el proceso de optimización del compilador justo a tiempo durante el tiempo de ejecución respalda la mejora continua de la eficiencia de ejecución del programa, mientras que el proceso de optimización del compilador frontal durante el período de compilación respalda la eficiencia de codificación del programador y la felicidad de los usuarios del lenguaje. mejorar

Compilador Javac

Proceso de compilación

  1. Proceso de preparación: inicialice el procesador de anotaciones del complemento
  2. El proceso de analizar y llenar la tabla de símbolos
    • Análisis léxico, gramatical, construcción de un árbol de sintaxis abstracta
    • Llene la tabla de símbolos, genere la dirección del símbolo y la información del símbolo
  3. El proceso de procesamiento de anotaciones del procesador de anotaciones del complemento: la fase de ejecución del procesador de anotaciones del complemento
  4. Proceso de análisis semántico y generación de bytecode
    • Verificación de anotaciones. Verifique la información estática de la gramática.
    • Flujo de datos y análisis de flujo de control. Verifique el proceso dinámico de ejecución del programa
    • Descifra el azúcar sintáctico. Reducir el azúcar sintáctico de la escritura de código simplificado a su forma original
    • Generación de códigos de bytes. Convertir la información generada en los pasos anteriores en bytecode

En las acciones anteriores, se pueden generar nuevos símbolos al ejecutar la inserción de anotaciones, si se generan nuevos símbolos se debe volver al análisis anterior y reprocesar estos nuevos símbolos durante el proceso de llenado de la tabla de símbolos.

Analizar y completar la tabla de símbolos

Análisis léxico y gramatical

El análisis léxico es el proceso de transformar el flujo de caracteres del código fuente en un conjunto de etiquetas. Una etiqueta es el elemento más pequeño en el momento de la compilación. Por ejemplo, int es una etiqueta

El análisis de sintaxis es el proceso de construir un árbol de sintaxis abstracto basado en la secuencia de etiquetas. El árbol de sintaxis abstracto es una representación de árbol utilizada para describir la estructura de sintaxis del código del programa

Llenar tabla de símbolos

La tabla de símbolos es una estructura de datos compuesta por un conjunto de direcciones de símbolos e información de símbolos, que se puede utilizar como una tabla hash almacenada en pares clave-valor.

Procesador de anotaciones

Procesador de anotaciones de complemento: las anotaciones generalmente funcionan en tiempo de ejecución, y esto avanza al período de compilación para procesar anotaciones específicas en el código, lo que afecta el proceso de trabajo del compilador de interfaz.

El procesador de anotaciones de complementos puede considerarse como un complemento del compilador. Si estos complementos modifican el árbol de sintaxis durante el procesamiento de las anotaciones, el compilador volverá al proceso de análisis y relleno de la tabla de símbolos y volverá a procesarla hasta que todas las anotaciones de los complementos El procesador ya no modifica el árbol de sintaxis y cada ciclo se denomina ronda.

El procesador de anotaciones enchufable puede lograr muchas funciones, como la generación automática de métodos getter / setter a través de métodos de anotaciones, equals (), hashCode ()

Análisis semántico y generación de bytecode

El árbol de sintaxis abstracta puede representar un programa fuente con la estructura correcta, pero no puede garantizar que la semántica del programa fuente sea lógica. La tarea principal del análisis semántico es verificar la naturaleza sensible al contexto del programa fuente con la estructura correcta, como la verificación de tipos, etc.

Al compilar, verá mensajes de error marcados con líneas rojas en el IDE, la mayoría de los cuales son resultados de la etapa de análisis semántico

Verificación de etiquetas

Compruebe si la variable está declarada antes de su uso, si el tipo de datos entre la variable y la asignación coincide, etc.

Plegado constante: int a=1+2se convertirá en a = 3

Análisis de flujo de datos y control

Compruebe cuestiones como si las variables locales del programa se asignan antes de su uso, si cada ruta del método tiene un valor de retorno, si todas las excepciones marcadas se manejan correctamente, etc.

Azúcar sintáctica

Agregar cierta gramática al lenguaje no tiene ningún efecto real sobre los resultados de la compilación y las funciones del lenguaje, pero es conveniente para los programadores usar el lenguaje. Reduzca la cantidad de código, aumente la legibilidad del programa y reduzca la posibilidad de errores en el código del programa

Generación de códigos de bytes

El método de instancia constructor () y la clase constructor () se agregan al árbol de sintaxis en esta etapa

La generación de (), () es el proceso de convergencia de código. El compilador convergerá el bloque de instrucciones, la inicialización de la variable, llamará al constructor de instancia de la clase principal y otras operaciones a sus dos métodos

Y asegúrese de que, independientemente del orden en el que aparezca el código fuente, debe ejecutarse en el orden de ejecutar primero el constructor de instancia de la clase principal, luego inicializar las variables y finalmente ejecutar el bloque de instrucciones.

El sabor del azúcar sintáctico de Java

Genérico

La esencia de los genéricos es la aplicación de tipos parametrizados o polimorfismos parametrizados. Es decir, el tipo de datos de la operación se puede especificar como un parámetro especial en la firma del método.

Los genéricos de Java son "genéricos de borrado de tipo", que solo aparecen en el código fuente del programa. En el archivo de código de bytes compilado, todos los genéricos se reemplazan con el tipo original sin procesar (Raw Type), y en el correspondiente El código de conversión obligatorio se inserta en el lugar

Entonces ArrayList y ArrayList son del mismo tipo en tiempo de ejecución

Tanto en términos de efecto de uso como de eficiencia operativa, va por detrás de la realización de genéricos.La única ventaja es que la realización de borrado de genéricos solo necesita mejorarse en el compilador de Java ...

Tipo de borrado

Java opta por generalizar los tipos existentes, como la generalización in situ de ArrayList a
tipo desnudo de ArrayList debe considerarse como el tipo principal común de
todas las instancias genéricas de este tipo. Permita que todos los tipos de instancia genéricos, como ArrayList, ArrayList Todos se convierten automáticamente en subtipos de ArrayList
La implementación de los tipos desnudos de Java: restaura ArrayList a ArrayList de manera simple y grosera en el momento de la compilación, e inserta automáticamente alguna conversión de tipo obligatoria y comprueba las instrucciones al acceder o modificar elementos.

Sin embargo, el llamado borrado en el método de borrado es solo para borrar el bytecode en el atributo Code del método, de hecho, la información genérica aún se conserva en los metadatos. Por tanto, el tipo parametrizado puede obtenerse mediante reflexión al codificar

problema

No admite datos primitivos

Debido a que no admite la transformación obligatoria entre los tipos básicos de int y Object, una vez que se borra la información genérica, el código de transformación obligatorio no se puede insertar hasta el lugar. Java no es compatible con los genéricos de los tipos primitivos y solo puede usar ArrayList, lo que genera la sobrecarga de empaquetado y desembalaje de innumerables clases de empaquetado de construcción.

Código prolijo

No se puede obtener información de tipo genérico durante el tiempo de ejecución

Trae una ambigüedad

Cuando los genéricos encuentran una sobrecarga

Embalaje, desembalaje, recorrido de bucle automático

El recorrido de bucle es restaurar el código a la implementación de iteradores, por lo que las clases que deben atravesarse implementan la interfaz de iterador

El parámetro de longitud variable se convierte en un parámetro de tipo de matriz al llamar

Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c==d);//true
System.out.println(e==f);//false
System.out.println(c==(a+b));//true
System.out.println(c.equals(a+b));//true
System.out.println(g==(a+b));//true
System.out.println(g.equals(a+b));//false

Dado que la operación == de la clase de empaquetado no se descomprimirá automáticamente sin encontrar operaciones aritméticas, y su método equals () no se ocupa de la relación de transformación de datos, intente evitar dicho empaquetado y desempaquetado automático en la codificación real

Compilación condicional

Método de compilación del lenguaje Java: el compilador no compila los archivos java uno por uno, sino que ingresa el nodo superior del árbol de sintaxis de todas las unidades de compilación en la lista para ser procesadas y luego compila, por lo que cada archivo puede proporcionar información simbólica entre sí.

Compilación condicional de Java: use una instrucción if con una condición constante, se ejecutará en tiempo de compilación


public static void main(String[] args)
{
    
    
    if(true)
    {
    
    
        System.out.println("1");
    }
    else
    {
    
    
        System.out.println("2");
    }
}

El resultado de la descompilación del archivo de clase después de compilar el código:

public static void main(String[] args)
{
    
    
   System.out.println("1");
}

El compilador eliminará el código inválido en la rama.

Este tipo de azúcar sintáctico solo se puede escribir dentro del cuerpo del método y solo puede lograr la compilación condicional en el nivel de bloque básico de la declaración, pero no hay forma de ajustar la estructura de toda la clase Java de acuerdo con las condiciones.

java
public static void main (String [] args)
{ System.out.println ("1"); }


编译器将会把分支中不成立的代码消除掉。

这种语法糖只能写在方法体内部,只能实现语句基本块级别的条件编译,而没有办法实现根据条件调整整个Java类的结构

Supongo que te gusta

Origin blog.csdn.net/weixin_42249196/article/details/108295165
Recomendado
Clasificación