Почему класс Java компилируется по-разному с пустой строкой?


у меня есть следующий класс 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 игнорирует пустые строки, почему скомпилированный байтовый код отличается для обеих программ?

а именно разница в классах заключается в том, что в конце-передачи (^D) символ заменяется символом конца текста (^C).

4 129

4 ответа:

в основном, номера строк сохраняются для отладки, поэтому, если вы измените исходный код так, как вы это сделали, ваш метод начинается с другой строки, и скомпилированный класс отражает разницу.

вы можете увидеть изменения с помощью 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 является необязательным атрибутом переменной длины в таблице атрибутов атрибута кода (§4.7.3). Он может использоваться отладчиками для определения того, какая часть массива кода соответствует заданному номеру строки в исходном исходном файле.

Если в таблице атрибутов атрибута кода присутствует несколько атрибутов LineNumberTable, то они могут отображаться в любом порядке.

в таблице атрибутов атрибута кода может быть более одного атрибута LineNumberTable на строку исходного файла. То есть атрибуты 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", независимо от количества строк.

а также любые сведения о номере строки для отладки, ваш манифест может также хранить время и дату сборки. Это, естественно, будет отличаться каждый раз при компиляции.