为什么Java类用空白行进行不同的编译?

时间:2018-10-03 10:35:20

标签: java compilation javac bytecode

我有以下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

我在this TutorialsPoint article上读过:

  

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

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

HelloWorld.class字节中的0x03字节被0x04字节所代替。

4 个答案:

答案 0 :(得分:325)

基本上,行号是为了调试而保留的,因此,如果以这种方式更改源代码,则方法将从另一行开始,并且编译后的类会反映出差异。

答案 1 :(得分:111)

您可以使用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属性可以一起代表源文件的给定行,而不必与源行一一对应。

答案 2 :(得分:51)

“ 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,并且程序总是打印{{ 1}},无论换行的数量如何。

答案 3 :(得分:11)

以及用于调试的任何行号详细信息,清单还可以存储构建时间和日期。每次编译时,自然会有所不同。