为什么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

我在这篇tutorialpoint文章上读到:

只包含空白(可能带有注释)的行称为空行,Java完全忽略它。

所以我的问题是,既然Java忽略空行,为什么两个程序的编译字节码不同?

也就是说,在HelloWorld.class中,0x03字节被0x04字节取代。

15943 次浏览

基本上,行号是为了调试而保留的,因此,如果您按照您所做的方式更改源代码,您的方法将从不同的行开始,而已编译的类将反映出差异。

你可以通过使用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属性,则它们可以以任何顺序出现。

在Code属性的属性表中,源文件的每行可能有多个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",而不管换行的数量。