Ответ 1
В основном номера строк сохраняются для отладки, поэтому, если вы изменили исходный код так, как вы это делали, ваш метод начинается с другой строки, и скомпилированный класс отражает разницу.
У меня есть следующий класс Java
public class HelloWorld {
public static void main(String []args) {
}
}
Когда я компилирую этот файл и запускаю sha256 в результирующем файле класса, я получаю
9c8d09e27ea78319ddb85fcf4f8085aa7762b0ab36dc5ba5fd000dccb63960ff HelloWorld.class
Затем я изменил класс и добавил пустую строку следующим образом:
public class HelloWorld {
public static void main(String []args) {
}
}
Снова я запустил sha256 на выходе, ожидая получить тот же результат, но вместо этого я получил
11f7ad3ad03eb9e0bb7bfa3b97bbe0f17d31194d8d92cc683cfbd7852e2d189f HelloWorld.class
Я прочитал в этой статье TutorialsPoint, что:
Строка, содержащая только пробел, возможно с комментарием, называется пустой строкой, и Java полностью игнорирует ее.
Поэтому мой вопрос заключается в том, что, поскольку Java игнорирует пустые строки, почему скомпилированный байт-код отличается для обеих программ?
А именно, разница в том, что в HelloWorld.class
0x03
байт заменяется байтом 0x04
.
В основном номера строк сохраняются для отладки, поэтому, если вы изменили исходный код так, как вы это делали, ваш метод начинается с другой строки, и скомпилированный класс отражает разницу.
Вы можете увидеть изменение с помощью javap -v
который выведет подробную информацию. Как и другие, упомянутые выше, разница будет в строках:
$ 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:
Точнее, файл класса отличается в разделе LineNumberTable
:
Атрибут LineNumberTable является необязательным атрибутом переменной длины в таблице атрибутов атрибута Code (§4.7.3). Он может использоваться отладчиками для определения того, какая часть массива кода соответствует заданному номеру строки в исходном исходном файле.
Если в таблице атрибутов атрибута Code присутствует несколько атрибутов LineNumberTable, они могут отображаться в любом порядке.
В строке исходного файла может быть более одного атрибута LineNumberTable в таблице атрибутов атрибута Code. То есть атрибуты LineNumberTable могут вместе представлять заданную строку исходного файла и не должны быть взаимно однозначными с исходными строками.
Предположение о том, что "Java игнорирует пустые строки", неверно. Вот фрагмент кода, который ведет себя по-разному в зависимости от количества пустых строк до 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]);
}
}
Если нет пустых строк до main
, он печатает "foo"
, но с одной пустой строкой до main
, он печатает "bar"
.
Поскольку поведение во время выполнения отличается, .class
должны быть разными, независимо от временных меток или других метаданных.
Это выполняется для каждого языка, который имеет доступ к кадрам стека с номерами строк, а не только для Java.
Примечание: если он скомпилирован с -g:none
(без какой-либо информации отладки), то номера строк не будут включены, getLineNumber()
всегда возвращает -1
, и программа всегда печатает "bar"
, независимо от количества разрывы строк.
Как и любые номера строк для отладки, ваш манифест также может хранить время и дату сборки. Естественно, это будет каждый раз при компиляции.