web-dev-qa-db-ja.com

なぜ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は空白行を無視するので、コンパイルされたバイトコードが両方のプログラムで異なるのはなぜですか。

つまり、HelloWorld.class内の0x03バイトが0x04バイトに置き換えられているという違いがあります。

203
KNejad

基本的には、行番号はデバッグ用に保存されているので、ソースコードを変更した場合、メソッドは別の行から始まり、コンパイルされたクラスはその違いを反映します。

325

詳細な情報を出力する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属性の属性テーブルには、ソースファイルの1行に複数のLineNumberTable属性があります。つまり、LineNumberTable属性は、ソースファイルの特定の行をまとめて表すことができ、ソース行と1対1である必要はありません。

111
Karol Dowbecki

"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の前に1行の空白行がある場合は"bar"を出力します。

実行時の動作が異なるため、タイムスタンプやその他のメタデータにかかわらず、.classファイルmustは異なります。

これは、Javaだけでなく、行番号の付いたスタックフレームにアクセスできるすべての言語に当てはまります。

注:-g:none(デバッグ情報なし)でコンパイルした場合、行番号は含まれず、getLineNumber()は常に-1を返し、プログラムは改行数に関係なく常に"bar"を出力します。

51
Andrey Tyukin

デバッグ用の行番号の詳細と同様に、マニフェストにはビルドの日時も格納されています。コンパイルするたびに当然これは異なります。

11
Graham