È meglio controllare `c> = '0'` o` c> = 48`?


46

Dopo una discussione con alcuni miei colleghi, ho una domanda "filosofica" su come trattare il tipo di dati char in Java, seguendo le migliori pratiche.

Supponiamo uno scenario semplice (ovviamente questo è solo un esempio molto semplice per dare un significato pratico alla mia domanda) in cui, dato uno String 's' come input, devi contare il numero di caratteri numerici presenti in esso.

Queste sono le 2 possibili soluzioni:

1)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= 48 && s.charAt(i) <= 57) {
            n++;
        }
    }

2)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= '0' && s.charAt(i) <= '9' ) {
            n++;
        }
    }

Quale dei due è più "pulito" e conforme alle migliori pratiche Java?


141
Perché dovresti scrivere 48 e 57 quando intendi davvero '0' e '9'? Scrivi quello che vuoi dire.
Brandin,

9
Aspetta cosa stai facendo, Java ha le VK_costanti che dovresti usare, in secondo luogo usare i codici char è meglio di char Java è un linguaggio sicuro di tipo che non dovresti fare tra i controlli incrociati. @Brandin Si chiama pratiche di codifica
Martin Barker,

12
Senza preoccuparsi di fare altro che giudicare le 6 persone che hanno pensato che questa fosse una buona domanda. Stai usando i caratteri come numeri? In tal caso, utilizzare i numeri. Lo stai usando come lettere? In tal caso, usa le lettere.
Alec Teal,

17
@MartinBarker Le VK_*costanti corrispondono a chiavi e non a caratteri .
CodesInChaos,

2
Mi ci sono voluti alcuni minuti per determinare cosa fa questo codice in relazione alla tua domanda. Già non è chiaro perché si presuppone che io sappia in (1) che questo è l'intervallo di cifre di ISO-Latino 1. Quindi questo lo rende problematico dal punto di vista della manutenzione.
CyberSkull,

Risposte:


124

Entrambi sono orribili, ma il primo è più orribile.

Entrambi ignorano la capacità integrata di Java di decidere quali caratteri sono "numerici" (tramite metodi in Character). Ma il primo non ignora solo la natura Unicode di stringhe, assumendo che non ci può essere solo 0123456789, ma anche oscura persino questo ragionamento non valida utilizzando codici di carattere che hanno senso solo se si sa qualcosa sulla storia di codifiche dei caratteri.


33
Perché stai supponendo che non rifiutare le cifre non ASCII sia sbagliato? Dipende dal contesto.
CodesInChaos,

21
@CodesInChaos Se vuoi davvero trovare caratteri numerici , scansionare 0123456789 è semplicemente sbagliato. Se in realtà vuoi cercare solo questi dieci caratteri, allora sono token essenzialmente privi di significato che sembrano casualmente familiari solo a persone che conoscono solo ASCII / ISO-latino. Non c'è nulla di sbagliato in questo - spesso devo fare esattamente questo, ad esempio per interagire con un software legacy che accetta davvero solo quei dieci personaggi. Ma allora dovresti chiarire le tue intenzioni usando qualcosa di simile matches("[0-9]+"), piuttosto che sfruttare il trucco gamma motivato storicamente.
Kilian Foth,

15
Esistono cifre a larghezza intera , che assomigliano alle cifre ASCII, e in generale è necessario un sacco di software per accettarle al posto delle cifre ASCII. (Ovviamente molti software sono rotti, a seconda della definizione di "molti". Si può facilmente dire perché i produttori di software in un paese trovano impossibile vendere in un altro paese perché i fornitori non rispettano i requisiti degli altri paesi. )
rwong

37
I have a Japanese IME installed , and accidentally type in full - width all the time.
BlueRaja - Danny Pflughoeft,

14
"Entrambi sono orribili", ma hai dimenticato di dire la soluzione giusta ;-)
Kromster dice di sostenere Monica il

163

Nessuno dei due. Lascia che la classe di caratteri integrata Java lo capisca per te.

for (int i = 0; i < s.length(); ++i) {
  if (Character.isDigit(s.charAt(i))) {
    ++n;
  }
}

Esistono alcuni intervalli di caratteri in più rispetto alle cifre ASCII che contano come cifre e nessuno degli esempi che hai pubblicato le conterà. Il JavaDoc per Character.isDigit()le liste di questi intervalli di caratteri come essendo di cifre valide:

Alcuni intervalli di caratteri Unicode che contengono cifre:

  • Da "\ u0030" a "\ u0039", cifre ISO-LATIN-1 (da "0" a "9")
  • Da "\ u0660" a "\ u0669", cifre arabo-indic
  • Da "\ u06F0" a "\ u06F9", cifre arabo-indic estese
  • Da "\ u0966" a "\ u096F", cifre di Devanagari
  • Da "\ uFF10" a "\ uFF19", cifre a larghezza intera

Molte altre gamme di caratteri contengono anche cifre.

Detto questo, si dovrebbe delegare Character.isDigit()anche con questo elenco. Man mano che vengono popolati nuovi piani Unicode, il codice Java verrà aggiornato. L'aggiornamento di JVM potrebbe far funzionare senza problemi il vecchio codice con caratteri di nuova cifra. È anche ASCIUTTO : localizzando il codice "è una cifra" in un punto a cui si fa riferimento altrove, è possibile evitare gli aspetti negativi della duplicazione del codice (ovvero i bug). Infine, nota l'ultima riga: questo elenco non è esaustivo e ci sono altre cifre.

Personalmente, preferirei delegare alle librerie Java di base e trascorrere il mio tempo in attività più produttive piuttosto che "capire cosa sia una cifra".


L'unica eccezione a questa regola è se hai davvero bisogno di testare le cifre letterali ASCII e non altre cifre. Ad esempio, se si sta analisi di un flusso e solo ASCII cifre (a differenza di altre cifre) hanno un significato speciale, allora sarebbe non opportuno l'uso Character.isDigit().

In tal caso, scriverei un altro metodo, ad esempio MyClass.isAsciiDigit()e inserirò la logica. Ottieni gli stessi vantaggi del riutilizzo del codice, il nome è super-chiaro su ciò che sta controllando e la logica è corretta.


4
Ottima risposta per fornire effettivamente il codice pulito che fa il trucco.
Pierre Arlaud,

27

Se mai scrivi un'applicazione in C che utilizza EBCDIC come set di caratteri di base e deve elaborare caratteri ASCII, usa 48e 57. Lo stai facendo? Io non la penso così.

Informazioni sull'uso isDigit(): dipende. Stai scrivendo un parser JSON? Solo 0a 9sono accettati come cifre, in modo da non utilizzare isDigit(), verificare la presenza di >= '0'e <= '9'. Stai elaborando l'input dell'utente? Utilizzare isDigit()fino a quando il resto del codice può effettivamente gestire la stringa e trasformarla in un numero correttamente.


3
In realtà è possibile scrivere applicazioni in Java che ottengono e restituiscono EBCDIC. Questo non è divertente.
Thorbjørn Ravn Andersen,

Analogo 'non divertente' stava attraversando il codice che era stato scritto usando i valori decimali dei caratteri EBCDIC durante la conversione in un ambiente multipiattaforma ...
Gwyn Evans,

1
Se stai elaborando dati EBCDIC in Java, probabilmente dovresti convertirli nel set di caratteri UTF-16 nativo di Java prima di elaborarli come caratteri. Ma immagino che dipenda davvero dall'applicazione; speriamo che se il tuo programma ha a che fare con EBCDIC, allora capirai cosa bisogna fare.
Michael Burr,

1
Il punto principale è che per l'elaborazione di EBCDIC in Java sia "0" che 48 sono errati nel rilevare un numero zero. Più corrente, in C, C ++ ecc. '\ N' e '\ r' sono definiti dall'implementazione, quindi se si desidera rilevare una coppia CR / LF di Windows in un file usando un compilatore non Windows, controllare meglio i valori decimali invece di verifica '\ n' e '\ r'.
gnasher729,

12

Il secondo esempio è chiaramente superiore. Il significato del secondo esempio è immediatamente evidente quando si guarda il codice. Il significato del primo esempio è ovvio solo se hai memorizzato l'intera tabella ASCII nella tua testa.

È necessario distinguere tra il controllo di un personaggio specifico o il controllo di un intervallo o di una classe di caratteri.

1) Verifica di un personaggio specifico.

Per i caratteri ordinari, usa il carattere letterale, ad es if(ch=='z').... Se controlli contro caratteri speciali come tab o interruzione di riga, dovresti usare le escape, come if (ch=='\n').... Se il carattere da verificare è insolito (ad esempio, non immediatamente riconoscibile o non disponibile su una tastiera standard), è possibile utilizzare un codice esadecimale anziché il carattere letterale. Ma poiché un codice esadecimale è un "valore magico", lo estrarresti in una costante e lo documenteresti:

const char snowman = 0x2603; // snowman char used to detect encoding issues
...
if (ch==showman)...

I codici esadecimali sono il modo standard di specificare i codici dei caratteri.

2) Verifica di una classe o intervallo di caratteri

Non dovresti davvero farlo direttamente nel codice dell'applicazione, ma incapsularlo in una classe separata interessata solo alla classificazione dei caratteri. E dovresti essere diverso da questo, poiché le librerie esistono già per questo scopo e la classificazione dei caratteri è di solito più complessa di quanto pensi, almeno se consideri i caratteri al di fuori dell'intervallo ASCII.

Se sei preoccupato solo per i caratteri nell'intervallo ASCII, potresti usare i letterali dei caratteri in questa libreria, altrimenti probabilmente useresti i letterali esadecimali. Se si osserva il codice sorgente per la libreria di caratteri incorporata Java, si fa riferimento anche ai valori dei caratteri e agli intervalli usando esadecimali, poiché è così che sono specificati nello standard Unicode.


1
Consiglierei anche di scrivere il carattere letterale in esadecimale usando '\x2603'invece per essere esplicito che stai testando il valore di un personaggio con una codifica esadecimale e non solo un numero casuale.
wefwefa3,

-4

È sempre meglio usarlo c >= '0'perché per c >= 48te è necessario convertire c in codice ASCII.


3
Cosa indica questa risposta che non era già stata detta nelle risposte precedenti di una settimana fa?

-5

Le espressioni regolari ( RegEx ) hanno una classe di caratteri specifica per le cifre - \d- che può essere utilizzata per rimuovere qualsiasi altro carattere dalla stringa. La lunghezza della stringa risultante è il valore desiderato.

public static int countDigits(String str) {
    str = Objects.requireNonNull(str).trim();

    return str.replaceAll("[^\\d]", "").length();
}

Si noti, tuttavia, che i RegEx sono più complessi dal punto di vista computazionale rispetto alle altre soluzioni proposte, pertanto non dovrebbero essere generalmente preferiti .


Modo molto elegante per fare il controllo!
Kevin Robatel,

I regex sono eccessivi per un compito come questo
Pharap,

2
@StefanoBragaglia Dopo aver riletto la tua risposta, penso che non risponda davvero alla domanda.
Pharap,

2
La tua risposta fornisce un modo diverso di risolvere il problema di "come contare le cifre in una stringa". Non risponde al problema di fondo con gli esempi di codice e la rappresentazione delle costanti, sia come numeri che come caratteri.

2
Questo in realtà non conta le cifre (ti dice solo qual è la lunghezza della stringa dopo che hai rimosso tutte le cifre, che non è né qui né lì), ma sono d'accordo che in realtà non risponde alla domanda. Ad esempio, nessuno chiedeva di rimuovere i caratteri dalle stringhe. La domanda è solo quella di chiedere il modo migliore per verificare se un personaggio è numerico.
doppelgreener,
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.