Perché è consentito eseguire il codice Java nei commenti con determinati caratteri Unicode?


1356

Il codice seguente produce l'output "Hello World!" (no davvero, provalo).

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

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

La ragione di ciò è che il compilatore Java analizza il carattere Unicode \u000dcome una nuova riga e viene trasformato in:

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

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

In tal modo, un commento viene "eseguito".

Dal momento che questo può essere usato per "nascondere" codice dannoso o qualunque cosa possa concepire un programmatore malvagio, perché è consentito nei commenti ?

Perché questo è consentito dalle specifiche Java?


44
"Perché è permesso" sembra essere troppo basato sull'opinione secondo me. I progettisti linguistici hanno preso una decisione, cos'altro c'è da sapere? A meno che non trovi una dichiarazione della persona che prende quella decisione, possiamo solo speculare.
Ingo Bürk,

194
Una cosa interessante è almeno che l'IDE di OP ovviamente sbaglia e mostra evidenziazione errata,
dhke

14
Possibilmente correlati: stackoverflow.com/questions/4448180/...
dhke

47
@Tobb Ma i designer Java stanno visitando SO, quindi è possibile ottenere risposte da uno di loro. Inoltre possono esistere risorse che già rispondono a questa domanda.
Pshemo,

41
La semplice risposta è che il codice non è affatto in un commento, secondo le regole della lingua, quindi la domanda non è corretta.
Marchese di Lorne,

Risposte:


741

La decodifica Unicode avviene prima di qualsiasi altra traduzione lessicale. Il vantaggio principale di questo è che rende banale andare avanti e indietro tra ASCII e qualsiasi altra codifica. Non hai nemmeno bisogno di capire dove iniziano e finiscono i commenti!

Come indicato nella sezione 3.3 di JLS, ciò consente a qualsiasi strumento basato su ASCII di elaborare i file di origine:

[...] Il linguaggio di programmazione Java specifica un modo standard di trasformare un programma scritto in Unicode in ASCII che cambia un programma in un modulo che può essere elaborato da strumenti basati su ASCII. [...]

Ciò offre una garanzia fondamentale per l'indipendenza della piattaforma (indipendenza dei set di caratteri supportati) che è sempre stato un obiettivo chiave per la piattaforma Java.

Essere in grado di scrivere qualsiasi carattere Unicode in qualsiasi parte del file è una caratteristica accurata, e particolarmente importante nei commenti, quando si documenta il codice in lingue non latine. Il fatto che possa interferire con la semantica in modi così sottili è solo un (sfortunato) effetto collaterale.

Ci sono molti gotcha su questo tema e Java Puzzlers di Joshua Bloch e Neal Gafter includevano la seguente variante:

È un programma Java legale? In tal caso, cosa stampa?

\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

(Questo programma risulta essere un semplice programma "Hello World".)

Nella soluzione al puzzle, sottolineano quanto segue:

Più seriamente, questo puzzle serve a rafforzare le lezioni delle tre precedenti: le fughe Unicode sono essenziali quando è necessario inserire personaggi che non possono essere rappresentati in nessun altro modo nel proprio programma. Evitali in tutti gli altri casi.


Fonte: Java: esecuzione del codice nei commenti ?!


84
In breve, quindi, Java lo consente intenzionalmente: il "bug" è nell'IDE dell'OP?
Bathsheba,

60
@Bathsheba: è più nelle teste delle persone. Le persone non cercano di capire come funziona l'analisi Java, quindi gli IDE a volte visualizzano il codice in modo errato. Nell'esempio sopra, il commento dovrebbe finire con \u000de la parte dopo dovrebbe avere evidenziazioni di codice.
Aaron Digulla,

62
Un altro errore comune è quello di incollare i percorsi di Windows nel codice come quello // C:\user\...che porta a un errore di compilazione poiché \usernon è una sequenza di escape Unicode valida.
Aaron Digulla,

50
In eclissi il codice dopo \u000dviene evidenziato parzialmente. Dopo aver premuto Ctrl + Maiusc + F il personaggio viene sostituito con una nuova riga e il resto della riga viene spostato
bluelDe

20
@TheLostMind Se capisco la risposta correttamente dovresti essere in grado di riprodurla anche con commenti a blocchi. \u002A/dovrebbe finire il commento.
Taemyr,

141

Dato che questo non è ancora stato risolto, ecco una spiegazione, perché la traduzione di Unicode sfugge avviene prima di qualsiasi altra elaborazione del codice sorgente:

L'idea alla base era che consente traduzioni senza perdita di codice sorgente Java tra diverse codifiche di caratteri. Oggi esiste un supporto Unicode diffuso e questo non sembra un problema, ma all'epoca non era facile per uno sviluppatore di un paese occidentale ricevere un codice sorgente dal suo collega asiatico contenente caratteri asiatici, apportare alcune modifiche ( inclusa la compilazione e il test) e la restituzione del risultato, il tutto senza danneggiare qualcosa.

Pertanto, il codice sorgente Java può essere scritto in qualsiasi codifica e consente un'ampia gamma di caratteri all'interno di identificatori, caratteri e Stringvalori letterali e commenti. Quindi, al fine di trasferirlo senza perdita di dati, tutti i caratteri non supportati dalla codifica di destinazione vengono sostituiti dai loro escape Unicode.

Questo è un processo reversibile e il punto interessante è che la traduzione può essere eseguita da uno strumento che non ha bisogno di sapere nulla sulla sintassi del codice sorgente Java poiché la regola di traduzione non dipende da essa. Questo funziona come la traduzione dei loro veri caratteri Unicode all'interno del compilatore avviene indipendentemente anche dalla sintassi del codice sorgente Java. Implica che puoi eseguire un numero arbitrario di passaggi di traduzione in entrambe le direzioni senza mai cambiare il significato del codice sorgente.

Questo è il motivo per un'altra strana caratteristica che non ha nemmeno menzionato: la \uuuuuuxxxxsintassi:

Quando uno strumento di traduzione sta sfuggendo ai caratteri e incontra una sequenza che è già una sequenza sfuggita, dovrebbe inserirne un ulteriore unella sequenza, convertendola \ucafein \uucafe. Il significato non cambia, ma quando si converte nella direzione opposta, lo strumento dovrebbe semplicemente rimuoverne uno ue sostituire solo le sequenze contenenti un singolo udai loro caratteri Unicode. In questo modo, anche le escape Unicode vengono mantenute nella loro forma originale durante la conversione avanti e indietro. Immagino che nessuno abbia mai usato quella funzione ...


1
È interessante notare native2asciiche non sembra usare la \uu...xxxxsintassi,
ninjalj

5
Sì, native2asciiaveva lo scopo di aiutare a preparare fasci di risorse convertendoli in iso-latino-1 comeProperties.load era fisso solo per leggere il latino-1. E lì, le regole sono diverse, nessuna \uuu…sintassi e nessuna fase di elaborazione iniziale. Nei file delle proprietà, property=multi\u000alineè effettivamente lo stesso di property=multi\nline. (In contraddizione con la frase "utilizzo di escape Unicode come definito nella sezione 3.3 di The Java ™ Language Specification" della documentazione)
Holger

10
Si noti che questo obiettivo di progettazione avrebbe potuto essere raggiunto senza nessuna delle verruche; il modo più semplice sarebbe stato di vietare le \ufughe per generare caratteri nell'intervallo U + 0000–007F. (Tutti questi personaggi possono essere rappresentati in modo nativo da tutte le codifiche nazionali che erano rilevanti negli anni '90 — beh, forse tranne alcuni dei personaggi di controllo, ma non hai bisogno di quelli per scrivere Java comunque.)
zwol

3
@zwol: bene, se si escludono comunque i caratteri di controllo che non sono ammessi nel codice sorgente Java, si ha ragione. Tuttavia, ciò significherebbe rendere le regole più complicate. E oggi è troppo tardi per discutere della decisione ...
Holger,

ah il problema di salvare un documento in utf8 e non in latino o qualcos'altro. Tutti i miei database sono stati rotti anche a causa di questa assurdità occidentale
David 天宇 Wong,

106

Aggiungerò completamente il punto inefficacemente, solo perché non posso fare a meno di me stesso e non l'ho ancora visto, che la domanda non è valida poiché contiene una premessa nascosta che è errata, vale a dire che il codice è in un commento!

Nel codice sorgente Java \ u000d è equivalente in ogni modo a un carattere CR ASCII. È un finale di riga, chiaro e semplice, ovunque si verifichi. La formattazione nella domanda è fuorviante, ciò a cui quella sequenza di caratteri corrisponde effettivamente sintatticamente è:

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

IMHO la risposta più corretta è quindi: il codice viene eseguito perché non è in un commento; è sulla riga successiva. "L'esecuzione del codice nei commenti" non è consentita in Java, proprio come ci si aspetterebbe.

Gran parte della confusione deriva dal fatto che gli evidenziatori della sintassi e gli IDE non sono abbastanza sofisticati da tenere conto di questa situazione. O non elaborano affatto le escape unicode o lo fanno dopo aver analizzato il codice anziché prima, come javacfa.


6
Sono d'accordo, questo non è un "errore di progettazione" java, ma è un bug IDE.
bvdb,

3
La domanda è piuttosto sul perché il codice che assomiglia a un commento a qualcuno che non ha familiarità con questo particolare aspetto del linguaggio e forse senza riferimento all'evidenziazione della sintassi, in realtà non è un commento. L'obiezione sulla base della premessa della domanda non è valida.
Phil

@Phil: sembra solo un commento se visto con strumenti particolari, altri lo mostrano diversamente.
jmoreno,

1
@jmoreno uno non dovrebbe avere di avere qualcosa di più di un editor di testo per leggere il codice. Per lo meno, viola il principio della minima sorpresa, vale a dire che i commenti // style continuano fino al carattere successivo \ n - non a qualsiasi altra sequenza che alla fine viene sostituita da \ n. I commenti non dovrebbero mai essere altro che spogliati. Pre-processore difettoso.
Phil

69

L' \u000descape termina un commento perché gli escape \uvengono convertiti uniformemente nei corrispondenti caratteri Unicode prima che il programma venga tokenizzato. Si potrebbe ugualmente utilizzare \u0057\u0057al posto di //per iniziare un commento.

Questo è un bug nel tuo IDE, che dovrebbe evidenziare la riga di sintassi per chiarire che\u000d termina il commento.

Questo è anche un errore di progettazione nella lingua. Non può essere corretto ora, perché ciò spezzerebbe i programmi che dipendono da esso. \ugli escape dovrebbero essere convertiti nel corrispondente carattere Unicode dal compilatore solo in contesti in cui ciò "ha senso" (valori letterali e identificativi di stringa, e probabilmente in nessun altro luogo) oppure dovrebbe essere vietato generare caratteri nell'intervallo U + 0000–007F , o entrambi. Ognuna di queste semantiche avrebbe impedito al commento di terminare con l' \u000descape, senza interferire con i casi in cui le \ufughe sono utili; nota che ciò include le uso delle \ufughe all'interno dei commenti come un modo per codificare i commenti in uno script non latino, perché il l'editor di testo potrebbe avere una visione più ampia di dove\ugli escape sono significativi rispetto al compilatore. (Non sono a conoscenza di alcun editor o IDE che mostrerà \uescape come caratteri corrispondenti in qualsiasi contesto.)

Esiste un errore di progettazione simile nella famiglia C, 1 in cui la barra rovesciata viene elaborata prima che vengano determinati i limiti dei commenti, quindi ad es.

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

Ho sollevato questo per illustrare che sembra facile commettere questo particolare errore di progettazione e non realizzare che è un errore fino a quando non è troppo tardi per correggerlo, se sei abituato a pensare alla tokenizzazione e ad analizzare il modo in cui i programmatori del compilatore pensano sulla tokenizzazione e l'analisi. Fondamentalmente, se hai già definito la tua grammatica formale e poi qualcuno presenta un caso sintattico speciale - trigrafi, barra rovesciata-newline, codifica di caratteri Unicode arbitrari in file di origine limitati ad ASCII, qualunque cosa - che devono essere uniti, è più facile aggiungi prima un passaggio di trasformazione del tokenizer piuttosto che ridefinire il tokenizer per prestare attenzione a dove ha senso usare quel caso speciale.

1 Per i pedanti: sono consapevole che questo aspetto di C era intenzionale al 100%, con la logica - non lo sto inventando - che ti permetterebbe di adattare meccanicamente il codice con linee arbitrariamente lunghe su schede perforate. È stata ancora una decisione di progettazione errata.


17
Non direi che si tratta di un errore di progettazione . Potrei essere d'accordo con te sul fatto che sia stata una scelta di progettazione scadente o una scelta con conseguenze sfavorevoli, ma penso ancora che funzioni come previsto dai progettisti del linguaggio: ti consente di utilizzare qualsiasi carattere unicode in qualsiasi parte del file, mantenendo la codifica ASCII del file.
Aioobe,

12
Detto questo, penso che la scelta della fase di elaborazione sia \ustata meno assurda della decisione di seguire l'esempio di C nell'usare zero iniziali per la notazione ottale. Mentre la notazione ottale a volte è utile, devo ancora sentire qualcuno esprimere un argomento sul perché uno zero iniziale è un buon modo per indicarlo.
supercat

3
@supercat Le persone che hanno lanciato quella caratteristica in C89 stavano generalizzando il comportamento del preprocessore K&R originale piuttosto che progettare una funzione da zero. Dubito che avessero familiarità con le migliori pratiche di schede perforate e dubito anche che la funzione sia mai stata utilizzata per lo scopo dichiarato, tranne forse per uno o due esercizi di calcolo retrò.
zwol,

8
@supercat Non avrei problemi con Java \ucome trasformazione pre-tokenizzazione se fosse proibito produrre caratteri nell'intervallo U + 0000..U + 007F. È la combinazione di "questo funziona ovunque" e "questo aliases caratteri ASCII con significato sintattico" che lo declassa da imbarazzante a sbagliato totale.
zwol,

4
Su "per i pedanti": ovviamente a quel tempo il //commento a riga singola non esisteva . E dal momento che C ha un terminatore dichiarazione che non è una nuova linea di, sarebbe in gran parte essere utilizzati per lunghe stringhe, salvo che, per quanto posso determinare "concatenazione di stringhe letterali" era lì da K & R.
Mark Hurd,

22

Questa è stata una scelta di design intenzionale che risale al design originale di Java.

A quelle persone che chiedono "chi vuole scappare Unicode nei commenti?", Presumo che siano persone la cui lingua madre usa il set di caratteri latino. In altre parole, è inerente al design originale di Java che le persone potrebbero usare caratteri Unicode arbitrari ovunque legali in un programma Java, più comunemente nei commenti e nelle stringhe.

È senza dubbio una carenza di programmi (come gli IDE) utilizzati per visualizzare il testo di origine che tali programmi non possono interpretare le fughe Unicode e visualizzare il glifo corrispondente.


8
Oggi usiamo UTF-8 per il nostro codice sorgente e possiamo usare direttamente i caratteri Unicode, senza bisogno di escape.
Paŭlo Ebermann,

21

Sono d'accordo con @zwol che questo è un errore di progettazione; ma ne sono ancora più critico.

\uescape è utile in letterali stringa e char; e questo è l'unico posto dove dovrebbe esistere. Dovrebbe essere gestito allo stesso modo di altre fughe come \n; e "\u000A" dovrebbe significare esattamente "\n".

Non ha assolutamente senso \uxxxxcommentare: nessuno può leggerlo.

Allo stesso modo, non ha senso utilizzare \uxxxxin altre parti del programma. L'unica eccezione è probabilmente nelle API pubbliche che sono costrette a contenere alcuni caratteri non ascii: qual è l'ultima volta che l'abbiamo visto?

I progettisti avevano le loro ragioni nel 1995, ma 20 anni dopo, questa sembra essere una scelta sbagliata.

(domanda ai lettori: perché questa domanda continua a ricevere nuovi voti? Questa domanda è collegata da qualche parte popolare?)


5
Immagino che tu non vada in giro, dove nelle API vengono usati caratteri non ASCII. Ci sono persone che lo usano (non io), ad esempio nei paesi asiatici. E quando si utilizzano caratteri non ASCII negli identificatori, vietarli nei commenti della documentazione ha poco senso. Tuttavia, consentirli all'interno di un token e consentire loro di cambiare il significato o il confine di un token sono cose diverse.
Holger,

15
possono usare la codifica dei file corretta. perché scrivere int \u5431quando puoi farloint 整
ZhongYu,

3
Cosa farai quando dovrai compilare il codice sulla loro API e non puoi usare la codifica corretta (supponi che non ci fosse UTF-8supporto diffuso nel 1995). Devi solo chiamare un metodo e non vuoi installare il pacchetto di supporto in lingua asiatica del tuo sistema operativo (ricorda, gli anni novanta) per quel singolo metodo ...
Holger,

5
Ciò che è molto più chiaro ora del 1995 è che è meglio conoscere l'inglese se si desidera programmare. La programmazione è un'interazione internazionale e quasi tutte le risorse sono in inglese.
ZhongYu,

8
Non penso che questo sia cambiato. Anche la documentazione di Java era in inglese per la maggior parte del tempo. C'è stata una traduzione giapponese mantenuta per un po ', ma mantenere due lingue in realtà non supporta l'idea di mantenerla per tutte le parti del mondo (piuttosto l'ha smentita). E prima ancora, non esisteva un linguaggio tradizionale con supporto Unicode negli identificatori. Quindi immagino che qualcuno abbia pensato che il codice sorgente localizzato fosse la prossima grande novità. Direi per fortuna , non è decollato.
Holger,

11

Le uniche persone che possono rispondere al motivo per cui le escape Unicode sono state implementate per così dire sono le persone che hanno scritto le specifiche.

Una ragione plausibile per questo è che c'era il desiderio di consentire all'intero BMP come possibili caratteri del codice sorgente Java. Ciò presenta tuttavia un problema:

  • Vuoi essere in grado di usare qualsiasi personaggio BMP.
  • Volete essere in grado di inserire qualsiasi carattere BMP ragionevolmente semplice. Un modo per farlo è con le escape Unicode.
  • Volete mantenere le specifiche lessicali facili da leggere e scrivere per gli esseri umani e ragionevolmente facili da implementare.

Questo è incredibilmente difficile quando gli escape Unicode entrano nella mischia: crea un intero carico di nuove regole lexer.

La semplice via d'uscita è fare il lessing in due passaggi: prima cerca e sostituisci tutti gli escape Unicode con il carattere che rappresenta, quindi analizza il documento risultante come se non esistessero escape Unicode.

Il vantaggio è che è facile da specificare, quindi semplifica le specifiche ed è facile da implementare.

Il rovescio della medaglia è, beh, il tuo esempio.


2
In alternativa, limitare l'uso di \ uxxxx a identificatori, valori letterali di stringa e costanti di caratteri. Questo è ciò che fa C11.
ninjalj,

questo complica davvero le regole del parser, perché sono quelle che definiscono quelle cose, che è quello che sto speculando fa parte del motivo per cui è così.
Martijn,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.