Tại sao thực thi mã Java trong các nhận xét với các ký tự Unicode nhất định được phép?


1356

Đoạn mã sau tạo ra đầu ra "Hello World!" (không thực sự, hãy thử nó).

public static void main(String... args) {

   // The comment below is not a typo.
   // \u000d System.out.println("Hello World!");
}

Lý do cho điều này là trình biên dịch Java phân tích ký tự Unicode \u000dthành một dòng mới và được chuyển đổi thành:

public static void main(String... args) {

   // The comment below is not a typo.
   //
   System.out.println("Hello World!");
}

Do đó dẫn đến một bình luận được "thực thi".

Vì điều này có thể được sử dụng để "che giấu" mã độc hoặc bất cứ điều gì một lập trình viên xấu xa có thể hình dung, tại sao nó lại được phép trong các bình luận ?

Tại sao điều này được cho phép bởi đặc tả Java?


44
"Tại sao điều này được cho phép" dường như quá dựa trên quan điểm đối với tôi. Các nhà thiết kế ngôn ngữ đã đưa ra một quyết định, những gì khác cần phải biết? Trừ khi bạn tìm thấy một tuyên bố của người đưa ra quyết định đó, chúng tôi chỉ có thể suy đoán.
Ingo Bürk

194
Một điều thú vị là ít nhất IDE của OP rõ ràng đã sai và hiển thị tô sáng không chính xác,
dhke


47
@Tobb Nhưng các nhà thiết kế Java đang truy cập SO để có thể nhận được câu trả lời bởi một trong số họ. Ngoài ra họ có thể tồn tại các tài nguyên đã trả lời câu hỏi này.
Pshemo

41
Câu trả lời đơn giản là mã hoàn toàn không có trong một nhận xét, bởi các quy tắc của ngôn ngữ, vì vậy câu hỏi không được định dạng đúng.
Hầu tước Lorne

Câu trả lời:


741

Giải mã Unicode diễn ra trước bất kỳ bản dịch từ vựng nào khác. Lợi ích chính của việc này là làm cho nó trở nên tầm thường khi qua lại giữa ASCII và bất kỳ mã hóa nào khác. Bạn thậm chí không cần phải tìm ra nơi bình luận bắt đầu và kết thúc!

Như đã nêu trong JLS Mục 3.3, điều này cho phép mọi công cụ dựa trên ASCII xử lý các tệp nguồn:

[...] Ngôn ngữ lập trình Java chỉ định một cách tiêu chuẩn để chuyển đổi một chương trình được viết bằng Unicode thành ASCII, thay đổi chương trình thành một hình thức có thể được xử lý bằng các công cụ dựa trên ASCII. [...]

Điều này mang lại sự đảm bảo cơ bản cho tính độc lập của nền tảng (tính độc lập của các bộ ký tự được hỗ trợ) luôn là mục tiêu chính của nền tảng Java.

Có thể viết bất kỳ ký tự Unicode nào ở bất kỳ đâu trong tệp là một tính năng gọn gàng và đặc biệt quan trọng trong các nhận xét, khi ghi lại mã bằng các ngôn ngữ không phải là tiếng Latin. Việc nó có thể can thiệp vào ngữ nghĩa theo những cách tinh tế như vậy chỉ là một tác dụng phụ (không may).

Có rất nhiều vấn đề về chủ đề này và Java Puzzlers của Joshua Bloch và Neal Gafter bao gồm các biến thể sau:

Đây có phải là một chương trình Java hợp pháp? Nếu vậy, nó in cái gì?

\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020
\u0063\u006c\u0061\u0073\u0073\u0020\u0055\u0067\u006c\u0079
\u007b\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020
\u0020\u0020\u0020\u0020\u0073\u0074\u0061\u0074\u0069\u0063
\u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028
\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u0020\u0020
\u0020\u0020\u0020\u0020\u0061\u0072\u0067\u0073\u0029\u007b
\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074
\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0020
\u0022\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u0022\u002b
\u0022\u006f\u0072\u006c\u0064\u0022\u0029\u003b\u007d\u007d

(Chương trình này hóa ra là một chương trình "Hello World" đơn giản.)

Trong giải pháp cho người đánh đố, họ chỉ ra những điều sau đây:

Nghiêm trọng hơn, câu đố này phục vụ để củng cố các bài học của ba phần trước: Thoát khỏi Unicode là điều cần thiết khi bạn cần chèn các ký tự không thể được thể hiện theo bất kỳ cách nào khác vào chương trình của bạn. Tránh chúng trong tất cả các trường hợp khác.


Nguồn: Java: Thực thi mã trong các bình luận?!


84
Nói tóm lại, Java cố tình cho phép nó: "lỗi" nằm trong IDE của OP?
Bathsheba

60
@Bathsheba: Nó nhiều hơn trong đầu của mọi người. Mọi người không cố gắng hiểu cách phân tích cú pháp Java hoạt động, do đó, IDE đôi khi hiển thị mã sai cách. Trong ví dụ trên, bình luận nên kết thúc bằng \u000dvà phần sau nó sẽ có mã nổi bật.
Aaron Digulla

62
Một lỗi phổ biến khác là dán các đường dẫn Windows vào mã giống như // C:\user\...dẫn đến lỗi biên dịch do đó \userkhông phải là một chuỗi thoát Unicode hợp lệ.
Aaron Digulla

50
Trong nhật thực, Mã sau \u000dđược tô sáng một phần. Sau khi nhấn Ctrl + Shift + F, ký tự được thay thế bằng dòng mới và phần còn lại của dòng được gói
bluelDe

20
@TheLostMind Nếu tôi hiểu câu trả lời chính xác, bạn cũng có thể sao chép câu hỏi này với các bình luận khối. \u002A/nên kết thúc bình luận.
Taemyr

141

Vì điều này chưa được giải quyết, nên đây là một lời giải thích, tại sao việc dịch mã Unicode lại xảy ra trước khi xử lý mã nguồn khác:

Ý tưởng đằng sau nó là nó cho phép các bản dịch mã nguồn Java không mất dữ liệu giữa các mã hóa ký tự khác nhau. Ngày nay, có sự hỗ trợ Unicode rộng rãi và điều này không có vẻ gì là vấn đề, nhưng hồi đó, nhà phát triển từ một quốc gia phương Tây không dễ dàng nhận được một số mã nguồn từ đồng nghiệp châu Á có chứa các ký tự châu Á, thực hiện một số thay đổi ( bao gồm biên dịch và kiểm tra nó) và gửi lại kết quả, tất cả mà không làm hỏng cái gì.

Vì vậy, mã nguồn Java có thể được viết bằng bất kỳ mã hóa nào và cho phép một loạt các ký tự trong các mã định danh, ký tự và nghĩa Stringđen và các bình luận. Sau đó, để chuyển nó một cách dễ dàng, tất cả các ký tự không được hỗ trợ bởi mã hóa đích được thay thế bằng các lần thoát Unicode của chúng.

Đây là một quá trình có thể đảo ngược và điểm thú vị là việc dịch có thể được thực hiện bởi một công cụ không cần biết gì về cú pháp mã nguồn Java vì quy tắc dịch không phụ thuộc vào nó. Điều này hoạt động như việc dịch sang các ký tự Unicode thực tế của chúng bên trong trình biên dịch cũng xảy ra độc lập với cú pháp mã nguồn Java. Nó ngụ ý rằng bạn có thể thực hiện một số bước dịch tùy ý theo cả hai hướng mà không bao giờ thay đổi ý nghĩa của mã nguồn.

Đây là lý do cho một tính năng kỳ lạ khác thậm chí chưa được đề cập: \uuuuuuxxxxcú pháp:

Khi một công cụ dịch đang thoát các ký tự và gặp một chuỗi đã là một chuỗi đã thoát, nó sẽ chèn một bổ sung uvào chuỗi, chuyển đổi \ucafethành \uucafe. Ý nghĩa không thay đổi, nhưng khi chuyển đổi sang hướng khác, công cụ chỉ nên loại bỏ một uvà chỉ thay thế các chuỗi có chứa một uký tự bằng các ký tự Unicode của chúng. Bằng cách đó, ngay cả các lối thoát Unicode cũng được giữ lại ở dạng ban đầu khi chuyển đổi qua lại. Tôi đoán, không ai từng sử dụng tính năng đó


1
Thật thú vị, native2asciidường như không sử dụng \uu...xxxxcú pháp,
ninjalj

5
Vâng, native2asciiđược dự định để giúp chuẩn bị các gói tài nguyên bằng cách chuyển đổi chúng thành iso-latin-1 như Properties.loadđã được cố định để chỉ đọc latin-1. Và ở đó, các quy tắc là khác nhau, không có \uuu…cú pháp và không có giai đoạn xử lý sớm. Trong các tập tin tài sản, property=multi\u000alinethực sự là giống như property=multi\nline. (Mâu thuẫn với cụm từ sử dụng Unicode thoát ra như được định nghĩa trong phần 3.3 của Đặc tả ngôn ngữ Java ™ của tài liệu)
Holger

10
Lưu ý rằng mục tiêu thiết kế này có thể đạt được mà không có bất kỳ mụn cóc nào; cách dễ nhất có thể là cấm \uthoát để tạo các ký tự trong phạm vi U + 0000 trừ007F. (Tất cả các nhân vật như vậy có thể được biểu diễn tự nhiên của tất cả các bảng mã quốc gia là có liên quan trong năm 1990-well, có lẽ ngoại trừ một số các ký tự điều khiển, nhưng bạn không cần những để viết Java anyway.)
Zwol

3
@zwol: tốt, nếu bạn loại trừ các ký tự điều khiển không được phép trong mã nguồn Java, bạn đã đúng. Tuy nhiên, nó sẽ ngụ ý làm cho các quy tắc phức tạp hơn. Và hôm nay, đã quá muộn để thảo luận về quyết định
Holger

ah vấn đề lưu tài liệu trong utf8 chứ không phải tiếng Latin hay gì khác. Tất cả các cơ sở dữ liệu của tôi cũng bị phá vỡ vì sự vô nghĩa của phương tây này
David Wong

106

Tôi sẽ hoàn toàn bổ sung một cách vô hiệu quả, chỉ vì tôi không thể tự giúp mình và tôi chưa thấy nó được đưa ra, rằng câu hỏi không hợp lệ vì nó chứa một tiền đề ẩn là sai, cụ thể là mã nằm trong một lời bình luận!

Trong mã nguồn Java \ u000d tương đương về mọi mặt với ký tự CR ASCII. Nó là một dòng kết thúc, đơn giản và đơn giản, bất cứ nơi nào nó xảy ra. Định dạng trong câu hỏi là sai lệch, chuỗi ký tự đó thực sự tương ứng về mặt cú pháp là gì:

public static void main(String... args) {
   // The comment below is no typo. 
   // 
 System.out.println("Hello World!");
}

Do đó, IMHO câu trả lời đúng nhất là: mã thực thi vì nó không nằm trong một nhận xét; nó ở dòng tiếp theo. "Thực thi mã trong các bình luận" không được phép trong Java, giống như bạn mong đợi.

Phần lớn sự nhầm lẫn xuất phát từ thực tế là các công cụ tô sáng cú pháp và IDE không đủ tinh vi để tính đến tình huống này. Họ hoặc không xử lý thoát unicode, hoặc họ làm điều đó sau khi phân tích mã thay vì trước đó, như thế javac.


6
Tôi đồng ý, đây không phải là "lỗi thiết kế" của java, nhưng đó là lỗi IDE.
bvdb

3
Câu hỏi là về lý do tại sao mã trông giống như một nhận xét cho một người không quen thuộc với khía cạnh đặc biệt này của ngôn ngữ và có lẽ không tham chiếu đến cú pháp tô sáng, trên thực tế không phải là một nhận xét. Việc phản đối trên cơ sở tiền đề của câu hỏi là không hợp lệ là không rõ ràng.
Phil

@Phil: nó chỉ giống như một bình luận khi được xem bằng các công cụ cụ thể, những người khác hiển thị nó khác.
jmoreno

1
@jmoreno người ta không cần phải nhiều hơn một trình soạn thảo văn bản để đọc mã. Ít nhất, nó vi phạm hiệu trưởng ít gây ngạc nhiên nhất, đó là // các bình luận kiểu tiếp tục cho đến ký tự \ n tiếp theo - không phải bất kỳ chuỗi nào khác cuối cùng được thay thế bằng \ n. Bình luận không bao giờ được mong đợi là bất cứ điều gì khác hơn là tước. Tiền xử lý xấu.
Phil

69

Lối \u000dthoát chấm dứt một nhận xét vì các \ulối thoát được chuyển đổi thống nhất thành các ký tự Unicode tương ứng trước khi chương trình được mã hóa. Bạn có thể sử dụng như nhau \u0057\u0057thay vì //để bắt đầu một bình luận.

Đây là một lỗi trong IDE của bạn, cần đánh dấu cú pháp dòng để làm rõ rằng \u000dkết thúc bình luận.

Đây cũng là một lỗi thiết kế trong ngôn ngữ. Nó không thể được sửa chữa ngay bây giờ, vì điều đó sẽ phá vỡ các chương trình phụ thuộc vào nó. \ucác lối thoát nên được chuyển đổi thành ký tự Unicode tương ứng bởi trình biên dịch chỉ trong các ngữ cảnh trong đó "có nghĩa" (chuỗi ký tự và mã định danh, và có lẽ không ở đâu khác) hoặc chúng đã bị cấm tạo các ký tự trong phạm vi U + 0000 , hoặc cả hai. Một trong những ngữ nghĩa đó sẽ ngăn không cho bình luận bị chấm dứt bởi \u000dlối thoát, mà không can thiệp vào các trường hợp \uthoát là lưu ý hữu ích mà bao gồm sử dụng các \ulối thoát bên trong các bình luận như một cách để mã hóa các bình luận trong một tập lệnh không phải là tiếng Latinh, bởi vì soạn thảo văn bản có thể có một cái nhìn rộng hơn về nơi\uthoát là đáng kể hơn trình biên dịch nào. (Tuy nhiên, tôi không biết bất kỳ trình soạn thảo hoặc IDE nào sẽ hiển thị \uthoát như các ký tự tương ứng trong bất kỳ ngữ cảnh nào .)

Có một lỗi thiết kế tương tự trong họ C, 1 trong đó backslash-newline được xử lý trước khi xác định ranh giới nhận xét, ví dụ:

// this is a comment \
   this is still in the comment!

Tôi đưa ra điều này để minh họa rằng nó rất dễ gây ra lỗi thiết kế đặc biệt này và không nhận ra rằng đó là lỗi cho đến khi quá muộn để sửa nó, nếu bạn đã quen nghĩ về token hóa và phân tích cú pháp theo cách mà các lập trình viên nghĩ về mã thông báo và phân tích cú pháp. Về cơ bản, nếu bạn đã xác định ngữ pháp chính thức của mình và sau đó ai đó đưa ra một trường hợp đặc biệt cú pháp - tricles, backslash-newline, mã hóa các ký tự Unicode tùy ý trong các tệp nguồn giới hạn ở ASCII, bất cứ điều gì - cần được thêm vào, thì dễ dàng hơn thêm một vượt qua chuyển đổi trước mã thông báo hơn là xác định lại mã thông báo để chú ý đến nơi sử dụng trường hợp đặc biệt đó.

1 Đối với trẻ em: Tôi biết rằng khía cạnh này của C là có chủ ý 100%, với lý do - tôi không làm điều này - rằng nó sẽ cho phép bạn sử dụng mã phù hợp về mặt cơ học với các dòng dài tùy ý vào các thẻ đục lỗ. Đó vẫn là một quyết định thiết kế không chính xác.


17
Tôi sẽ không đi xa để nói rằng đó là một lỗi thiết kế . Tôi có thể đồng ý với bạn rằng đó là một lựa chọn thiết kế kém hoặc một lựa chọn có hậu quả đáng tiếc, nhưng tôi vẫn nghĩ rằng nó hoạt động như các nhà thiết kế ngôn ngữ dự định: Nó cho phép bạn sử dụng bất kỳ ký tự unicode nào ở bất kỳ đâu trong tệp, trong khi duy trì mã hóa ASCII của tập tin.
aioobe

12
Điều đó đã được nói, tôi nghĩ rằng sự lựa chọn của giai đoạn xử lý \ulà ít vô lý hơn so với quyết định đi theo sự dẫn dắt của C trong việc sử dụng các số 0 hàng đầu cho ký hiệu bát phân. Mặc dù ký hiệu bát phân đôi khi hữu ích, tôi vẫn chưa nghe ai nói rõ lý do tại sao số 0 đứng đầu là một cách hay để chỉ ra nó.
supercat

3
@supercat Những người đã ném tính năng đó vào C89 đang khái quát hóa hành vi của bộ tiền xử lý K & R ban đầu thay vì thiết kế một tính năng từ đầu. Tôi nghi ngờ họ đã quen thuộc với các thực tiễn tốt nhất về thẻ đục lỗ và tôi cũng nghi ngờ rằng tính năng này đã từng được sử dụng cho mục đích đã nêu, ngoại trừ có thể cho một hoặc hai bài tập tính toán ngược.
zwol

8
@supercat Tôi sẽ không gặp vấn đề gì với Java \ukhi chuyển đổi tiền mã hóa nếu nó bị cấm sản xuất các ký tự trong phạm vi U + 0000..U + 007F. Đó là sự kết hợp giữa "điều này hoạt động ở mọi nơi" và "điều này bí danh các ký tự ASCII có ý nghĩa cú pháp" làm giảm bớt sự khó xử thành sai lầm.
zwol

4
Trên "dành cho trẻ em" của bạn: Tất nhiên tại thời điểm đó , //nhận xét một dòng không tồn tại . Và vì C có một bộ kết thúc câu lệnh không phải là một dòng mới, nên nó chủ yếu được sử dụng cho các chuỗi dài, ngoại trừ theo như tôi có thể xác định "nối chuỗi theo nghĩa đen" đã có từ K & R.
Đánh dấu

22

Đây là một sự lựa chọn thiết kế có chủ ý quay trở lại thiết kế ban đầu của Java.

Đối với những người hỏi "ai muốn Unicode thoát khỏi các bình luận?", Tôi cho rằng họ là những người có ngôn ngữ bản địa sử dụng bộ ký tự Latinh. Nói cách khác, thiết kế ban đầu của Java là mọi người có thể sử dụng các ký tự Unicode tùy ý ở bất cứ nơi nào hợp pháp trong một chương trình Java, điển hình nhất là trong các nhận xét và chuỗi.

Có thể nói là một thiếu sót trong các chương trình (như IDE) được sử dụng để xem văn bản nguồn mà các chương trình đó không thể giải thích các lối thoát Unicode và hiển thị glyph tương ứng.


8
Ngày nay, chúng tôi sử dụng UTF-8 cho mã nguồn của mình và có thể sử dụng trực tiếp các ký tự Unicode, không cần phải thoát.
Paŭlo Ebermann

21

Tôi đồng ý với @zwol rằng đây là lỗi thiết kế; nhưng tôi thậm chí còn quan trọng hơn về nó.

\uthoát là hữu ích trong chuỗi và char charals; và đó là nơi duy nhất mà nó nên tồn tại. Nó nên được xử lý theo cách tương tự như các lối thoát khác như \n; và "\u000A" nên có nghĩa chính xác "\n".

Hoàn toàn không có điểm nào có \uxxxxý kiến ​​- không ai có thể đọc được điều đó.

Tương tự, không có điểm sử dụng \uxxxxtrong phần khác của chương trình. Ngoại lệ duy nhất có lẽ là trong các API công khai bị ép buộc có chứa một số ký tự không phải là ascii - lần cuối chúng ta đã thấy điều đó là gì?

Các nhà thiết kế đã có lý do của họ vào năm 1995, nhưng 20 năm sau, điều này dường như là một lựa chọn sai lầm.

(câu hỏi cho độc giả - tại sao câu hỏi này tiếp tục nhận được phiếu bầu mới? Câu hỏi này có được liên kết từ một nơi phổ biến không?)


5
Tôi đoán, bạn không đi loanh quanh, nơi các ký tự không phải ASCII được sử dụng trong API. Có những người sử dụng nó (không phải tôi), ví dụ như ở các nước châu Á. Và khi bạn đang sử dụng các ký tự không phải ASCII trong mã định danh, việc cấm chúng trong các nhận xét tài liệu có ý nghĩa rất nhỏ. Tuy nhiên, cho phép chúng bên trong mã thông báo và cho phép chúng thay đổi ý nghĩa hoặc ranh giới của mã thông báo là những điều khác nhau.
Holger

15
họ có thể sử dụng mã hóa tập tin thích hợp. tại sao viết int \u5431khi bạn có thể làmint 整
ZhongYu

3
Bạn sẽ làm gì khi bạn phải biên dịch mã theo API của họ và không thể sử dụng mã hóa phù hợp (giả sử rằng không có UTF-8hỗ trợ rộng rãi vào năm 1995). Bạn chỉ cần gọi một phương thức và không muốn cài đặt gói hỗ trợ ngôn ngữ châu Á của hệ điều hành của bạn (hãy nhớ, những năm chín mươi) cho phương thức duy nhất đó
Holger

5
Điều rõ ràng hơn nhiều so với năm 1995 là bạn biết tiếng Anh tốt hơn nếu bạn muốn lập trình. Lập trình là một sự tương tác quốc tế và hầu hết tất cả các tài nguyên đều bằng tiếng Anh.
ZhongYu

8
Tôi không nghĩ rằng điều này đã thay đổi. Tài liệu của Java hầu hết đều là tiếng Anh. Có một bản dịch tiếng Nhật được duy trì trong một thời gian nhưng việc duy trì hai ngôn ngữ không thực sự sao lưu ý tưởng duy trì nó cho tất cả các địa phương trên thế giới (nó không chấp nhận nó). Và trước đó, không có ngôn ngữ chính nào có hỗ trợ Unicode trong các định danh. Vì vậy, tôi đoán, ai đó nghĩ rằng mã nguồn địa phương là điều lớn tiếp theo. Tôi sẽ nói rất may , nó đã không cất cánh.
Holger

11

Những người duy nhất có thể trả lời tại sao thoát Unicode được thực hiện vì họ là những người đã viết đặc tả.

Một lý do chính đáng cho điều này là có mong muốn cho phép toàn bộ BMP là các ký tự có thể có của mã nguồn Java. Điều này trình bày một vấn đề mặc dù:

  • Bạn muốn có thể sử dụng bất kỳ nhân vật BMP.
  • Bạn muốn có thể nhập bất kỳ charater BMP nào một cách hợp lý dễ dàng. Một cách để làm điều này là với các lối thoát Unicode.
  • Bạn muốn giữ cho đặc tả từ vựng dễ dàng cho con người đọc và viết, và cũng dễ dàng thực hiện.

Điều này là vô cùng khó khăn khi Unicode thoát ra khỏi cuộc cạnh tranh: nó tạo ra một tải toàn bộ các quy tắc lexer mới.

Cách dễ dàng là thực hiện lexing theo hai bước: đầu tiên tìm kiếm và thay thế tất cả các lần thoát Unicode bằng ký tự mà nó đại diện, sau đó phân tích tài liệu kết quả như thể thoát Unicode không tồn tại.

Mặt trái của điều này là nó dễ xác định, do đó nó làm cho đặc tả đơn giản hơn và dễ thực hiện.

Nhược điểm là, tốt, ví dụ của bạn.


2
Hoặc, hạn chế sử dụng \ uxxxx cho số nhận dạng, chuỗi ký tự và hằng ký tự. Đó là những gì C11 làm.
ninjalj

điều đó thực sự làm phức tạp các quy tắc của trình phân tích cú pháp, bởi vì đó là những gì xác định những điều đó, đó là những gì tôi đang suy đoán là một phần của lý do nó là như vậy.
Martijn
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.