Tại sao một lớp Java biên dịch khác nhau với một dòng trống?


207

Tôi có lớp Java sau

public class HelloWorld {
  public static void main(String []args) {
  }
}

Khi tôi biên dịch tệp này và chạy sha256 trên tệp lớp kết quả, tôi nhận được

9c8d09e27ea78319ddb85fcf4f8085aa7762b0ab36dc5ba5fd000dccb63960ff  HelloWorld.class

Tiếp theo tôi đã sửa đổi lớp và thêm một dòng trống như thế này:

public class HelloWorld {

  public static void main(String []args) {
  }
}

Một lần nữa tôi chạy sha256 trên đầu ra với mong muốn nhận được kết quả tương tự nhưng thay vào đó tôi đã nhận được

11f7ad3ad03eb9e0bb7bfa3b97bbe0f17d31194d8d92cc683cfbd7852e2d189f  HelloWorld.class

Tôi đã đọc bài viết trên Hướng dẫn này rằng:

Một dòng chỉ chứa khoảng trắng, có thể có một nhận xét, được gọi là một dòng trống và Java hoàn toàn bỏ qua nó.

Vì vậy, câu hỏi của tôi là, vì Java bỏ qua các dòng trống tại sao mã byte được biên dịch lại khác nhau cho cả hai chương trình?

Cụ thể là sự khác biệt trong HelloWorld.classmột 0x03byte được thay thế bằng một 0x04byte.


45
Lưu ý rằng trình biên dịch không bắt buộc phải có tính xác định trong việc tạo các tệp lớp, mặc dù chúng là bình thường. Xem câu hỏi này . Các tệp Jar theo mặc định không thể sao chép được, tức là thậm chí biên dịch cùng một mã sẽ dẫn đến hai JAR khác nhau. Đó là bởi vì thứ tự của các tệp và dấu thời gian sẽ không khớp. Xây dựng sinh sản là có thể với cấu hình cụ thể.
Giacomo Alzetta

22
TutorialsPoint tuyên bố rằng "Java hoàn toàn bỏ qua" các dòng trống. Phần 3,4 của Đặc tả ngôn ngữ Java nói khác. Ai để tin? ...
skomisa

37
@skomisa Đặc điểm kỹ thuật.
wizzwizz4

4
@GiacomoAlzetta thậm chí không có biểu mẫu mã byte được chỉ định cho một tệp mã byte đơn. Ví dụ, thứ tự của các thành viên là không xác định, vì vậy nếu trình biên dịch sử dụng Sets bất biến mới với ngẫu nhiên trong nội bộ, nó có thể tạo ra một thứ tự khác nhau trên mỗi lần chạy. Nó cũng có thể thêm một thuộc tính tùy chỉnh chứa thời gian biên dịch. Và cứ thế trên đường
Holger

15
@DioPhung một bài học kinh nghiệm khác: tutspoint không phải là một nguồn đáng tin cậy cho các hướng dẫn tốt
jwenting

Câu trả lời:


331

Về cơ bản, số dòng được giữ để gỡ lỗi, vì vậy nếu bạn thay đổi mã nguồn theo cách bạn đã làm, phương thức của bạn bắt đầu ở một dòng khác và lớp được biên dịch phản ánh sự khác biệt.


11
Điều đó cũng giải thích tại sao sự khác biệt của nó trong Byte được báo cáo bởi OP: end-of-transmissionlà viết tắt của mã ASCII 4 và end-of-textlà viết tắt của mã ASCII 3
Ferrybig

160
Để chứng minh bằng thực nghiệm điều này, tôi đã so sánh giá trị băm của các tệp lớp của nguồn OP bằng cách sử dụng -g:nonecờ khi biên dịch (loại bỏ tất cả thông tin gỡ lỗi, xem tại đây ) và có cùng hàm băm trong cả hai kịch bản.
Thuyền trưởng Man

14
Để hỗ trợ chính thức của câu trả lời của bạn, từ phần 3.4 ( "Line Terminators" ) của ngôn ngữ Java Specification cho Java SE 11 : "trình biên dịch Java Một phân chia tiếp theo chuỗi các ký tự đầu vào Unicode vào dòng bằng cách nhận Terminators dòng ... Các dòng định nghĩa bởi các đầu cuối dòng có thể xác định số dòng được tạo bởi trình biên dịch Java " .
skomisa

4
Một cách sử dụng quan trọng của các số dòng này là nếu ném ngoại lệ; nó có thể cho bạn biết số dòng của ngoại lệ trong theo dõi ngăn xếp.
gparyani

114

Bạn có thể thấy sự thay đổi bằng cách sử dụng javap -vsẽ xuất thông tin dài dòng. Giống như đã đề cập khác, sự khác biệt sẽ là trong số dòng:

$ 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:

Chính xác hơn là tập tin lớp khác nhau trong LineNumberTablephần:

Thuộc tính LineNumberTable là thuộc tính độ dài biến tùy chọn trong bảng thuộc tính của thuộc tính Code (§4.7.3). Nó có thể được sử dụng bởi các trình gỡ lỗi để xác định phần nào của mảng mã tương ứng với một số dòng nhất định trong tệp nguồn gốc.

Nếu nhiều thuộc tính LineNumberTable có trong bảng thuộc tính của thuộc tính Code, thì chúng có thể xuất hiện theo bất kỳ thứ tự nào.

Có thể có nhiều hơn một thuộc tính LineNumberTable trên mỗi dòng của tệp nguồn trong bảng thuộc tính của thuộc tính Code. Nghĩa là, các thuộc tính LineNumberTable có thể cùng nhau đại diện cho một dòng nhất định của tệp nguồn và không cần phải là một đối một với các dòng nguồn.


57

Giả định rằng "Java bỏ qua các dòng trống" là sai. Dưới đây là đoạn mã hoạt động khác nhau tùy thuộc vào số lượng dòng trống trước phương thức 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]);
  }
}

Nếu không có dòng nào trống trước main, nó sẽ in "foo", nhưng với một dòng trống trước main, nó sẽ in "bar".

Vì hành vi thời gian chạy là khác nhau, các .classtệp phải khác nhau, bất kể dấu thời gian hoặc siêu dữ liệu khác.

Điều này giữ cho mọi ngôn ngữ có quyền truy cập vào các khung ngăn xếp với số dòng, không chỉ cho Java.

Lưu ý: nếu được biên dịch với -g:none(không có bất kỳ thông tin gỡ lỗi nào), thì số dòng sẽ không được đưa vào, getLineNumber()luôn trả về -1và chương trình luôn được in "bar", bất kể số lần ngắt dòng.


11
Nó cũng có thể in Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1.
xehpuk

1
@xehpuk Cách duy nhất tôi có thể nhận -1được là sử dụng -g:nonecờ. Có cách nào khác để có được ngoại lệ này bằng cách sử dụng thông thường javackhông?
Andrey Tyukin

3
Tôi đoán chỉ với -gtùy chọn. Cũng có -g:vars-g:sourceđiều đó ngăn cản việc tạo ra LineNumberTable.
xehpuk

14

Cũng như bất kỳ chi tiết số dòng nào để gỡ lỗi, bảng kê khai của bạn cũng có thể lưu trữ thời gian và ngày xây dựng. Điều này sẽ tự nhiên khác nhau mỗi khi bạn biên dịch.


14
C # cũng có vấn đề này; cho đến gần đây trình biên dịch luôn nhúng một GUID mới trong cụm được tạo để bạn được đảm bảo rằng hai bản dựng sẽ không giống nhau nhị phân, để bạn có thể phân biệt chúng!
Eric Lippert

3
@EricLippert nếu hai bản dựng chỉ khác nhau theo thời gian được tạo ra (tức là cơ sở mã giống hệt nhau), chúng ta có nên coi chúng là như nhau không? Với đường dẫn xây dựng CI / CD hiện đại (Jenkins, TeamCity, CircleCI), chúng tôi sẽ có cách để phân biệt giữa các bản dựng, nhưng từ góc độ ứng dụng, việc triển khai các nhị phân mới hơn với cơ sở mã giống hệt nhau dường như không hữu ích.
Dio Phung

2
@DioPhung Đó là cách khác. Bạn không muốn hai bản dựng khác nhau có cùng GUID, vì đó là cách hệ thống có thể quyết định sử dụng bản dựng nào. Vì vậy, dễ nhất để tạo GUID mới mỗi lần; và sau đó bạn nhận được tác dụng phụ mà Eric mô tả là hậu quả không lường trước được.
Graham

3
@vikingsteve Giống như tôi đã nói, sẽ rất ít hữu ích hơn khi hai bản dựng khác nhau được báo cáo với cùng một GUID, sau đó sẽ được báo cáo cho hệ thống là cùng một phần mềm. Điều này sẽ gây ra sự thất bại hoàn toàn cho bất kỳ loại kế hoạch cung cấp nào, do đó, nhiệm vụ quan trọng là GUID không bao giờ bị trùng lặp (trong xác suất hợp lý!). Có các GUID khác nhau cho hai bản dựng riêng biệt của cùng một mã nguồn là một sự phiền toái không đáng kể. Vì vậy, trong bối cảnh của một kịch bản thất bại nghiêm trọng về nhiệm vụ, những gì bạn nghĩ là hơi vô ích thực sự không thành công.
Graham

4
@vikingsteve Phần của nhị phân vẫn giống nhau (nếu tôi hiểu, tôi không phải là nhà phát triển C #), đó chỉ là một số siêu dữ liệu được gắn vào nhị phân.
Thuyền trưởng Man
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.