que - ¿Por qué una clase Java compila de manera diferente con una línea en blanco?
que es java (4)
Además de los detalles del número de línea para la depuración, su manifiesto también puede almacenar la fecha y la hora de construcción. Esto naturalmente será diferente cada vez que compiles.
Tengo la siguiente clase de Java
public class HelloWorld {
public static void main(String []args) {
}
}
Cuando compilo este archivo y ejecuto un sha256 en el archivo de clase resultante, obtengo
9c8d09e27ea78319ddb85fcf4f8085aa7762b0ab36dc5ba5fd000dccb63960ff HelloWorld.class
Luego modifiqué la clase y agregué una línea en blanco como esta:
public class HelloWorld {
public static void main(String []args) {
}
}
Nuevamente ejecuté un sha256 en la salida esperando obtener el mismo resultado pero en vez de eso obtuve
11f7ad3ad03eb9e0bb7bfa3b97bbe0f17d31194d8d92cc683cfbd7852e2d189f HelloWorld.class
He leído en este artículo de TutorialsPoint que:
Una línea que contiene solo espacios en blanco, posiblemente con un comentario, se conoce como una línea en blanco, y Java la ignora por completo.
Entonces, mi pregunta es que, dado que Java ignora las líneas en blanco, ¿por qué el código de bytes compilado es diferente para ambos programas?
Es decir, la diferencia en que en
HelloWorld.class
un byte
0x03
se reemplaza por un byte
0x04
.
Básicamente, los números de línea se guardan para la depuración, por lo que si cambia su código fuente de la manera en que lo hizo, su método comienza en una línea diferente y la clase compilada refleja la diferencia.
La suposición de que
"Java ignora las líneas en blanco"
es errónea.
Aquí hay un fragmento de código que se comporta de manera diferente dependiendo del número de líneas vacías antes del método
main
:
class NewlineDependent {
public static void main(String[] args) {
int i = Thread.currentThread().getStackTrace()[1].getLineNumber();
System.out.println((new String[]{"foo", "bar"})[((i % 2) + 2) % 2]);
}
}
Si no hay líneas vacías antes de la página
main
, imprime
"foo"
, pero con una línea vacía antes de la línea
main
, imprime
"bar"
.
Dado que el comportamiento en tiempo de ejecución es diferente, los archivos
.class
deben
ser diferentes, independientemente de las marcas de tiempo u otros metadatos.
Esto es válido para todos los idiomas que tienen acceso a los marcos de pila con números de línea, no solo para Java.
Nota: si se compila con
-g:none
(sin información de depuración), los números de línea no se incluirán,
getLineNumber()
siempre devuelve
-1
, y el programa siempre imprime
"bar"
, independientemente del número de saltos de línea .
Puede ver el cambio utilizando
javap -v
que generará información detallada.
Como otros ya mencionados, la diferencia estará en los números de línea:
$ javap -v HelloWorld.class > with-line.txt
$ javap -v HelloWorld.class > no-line.txt
$ diff -C 1 no-line.txt with-line.txt
*** no-line.txt 2018-10-03 11:43:32.719400000 +0100
--- with-line.txt 2018-10-03 11:43:04.378500000 +0100
***************
*** 2,4 ****
Last modified 03-Oct-2018; size 373 bytes
! MD5 checksum 058baea07fb787bdd81c3fb3f9c586bc
Compiled from "HelloWorld.java"
--- 2,4 ----
Last modified 03-Oct-2018; size 373 bytes
! MD5 checksum 435dbce605c21f84dda48de1a76e961f
Compiled from "HelloWorld.java"
***************
*** 50,52 ****
LineNumberTable:
! line 3: 0
LocalVariableTable:
--- 50,52 ----
LineNumberTable:
! line 4: 0
LocalVariableTable:
Más precisamente, el archivo de clase difiere en la sección
LineNumberTable
:
El atributo LineNumberTable es un atributo opcional de longitud variable en la tabla de atributos de un atributo de Código (§4.7.3). Puede ser utilizado por los depuradores para determinar qué parte de la matriz de códigos corresponde a un número de línea dado en el archivo fuente original.
Si hay varios atributos de LineNumberTable en la tabla de atributos de un atributo de Código, entonces pueden aparecer en cualquier orden.
Puede haber más de un atributo LineNumberTable por línea de un archivo de origen en la tabla de atributos de un atributo de Código. Es decir, los atributos LineNumberTable pueden representar juntos una línea determinada de un archivo de origen y no necesitan ser uno a uno con las líneas de origen.