È 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) dove, data una stringa '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 delle due è più "pulita" e conforme alle best practice Java?

    
posta wyr0 25.11.2015 - 14:36
fonte

6 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 solo ignora la natura Unicode delle stringhe, supponendo che ci possa essere solo 0123456789, anche anche oscura anche questo ragionamento non valido utilizzando codici di carattere che hanno senso solo se si conosce qualcosa sulla storia delle codifiche dei caratteri.

    
risposta data 25.11.2015 - 14:42
fonte
163

Nessuno dei due. Lascia che sia la classe di Personaggio a essere integrata in Java per te.

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

Ci sono altri intervalli di caratteri oltre alle cifre ASCII che contano come cifre, e nessuno degli esempi che hai postato li conterà. Il JavaDoc per Character.isDigit() elenca questi caratteri intervalli come cifre valide:

Some Unicode character ranges that contain digits:

  • '\u0030' through '\u0039', ISO-LATIN-1 digits ('0' through '9')
  • '\u0660' through '\u0669', Arabic-Indic digits
  • '\u06F0' through '\u06F9', Extended Arabic-Indic digits
  • '\u0966' through '\u096F', Devanagari digits
  • '\uFF10' through '\uFF19', Fullwidth digits

Many other character ranges contain digits as well.

Detto questo, si dovrebbe delegare a Character.isDigit() anche con questo elenco. Man mano che i nuovi piani Unicode vengono popolati, il codice Java verrà aggiornato. L'aggiornamento della JVM potrebbe far sì che il vecchio codice funzioni perfettamente con i nuovi caratteri numerici. È anche DRY : localizzando il codice "è questa una cifra" in un posto cui si fa riferimento altrove, gli aspetti negativi di duplicazione del codice (cioè bug) può essere evitato. Infine, nota l'ultima riga: questo elenco non è esaustivo e ci sono altre cifre.

Personalmente, preferirei delegare alle librerie Java core e passare il mio tempo a compiti più produttivi di "capire che cos'è una cifra".

L'unica eccezione a questa regola è se hai davvero bisogno di testare le cifre ASCII letterali e non altre cifre. Ad esempio, se stai analizzando un flusso e solo cifre ASCII (rispetto ad altre cifre) hanno un significato speciale, allora non essere appropriato per usare Character.isDigit() .

In tal caso, scriverei un altro metodo, ad es. MyClass.isAsciiDigit() e inserisci la logica. Ottieni gli stessi vantaggi del riutilizzo del codice, il nome è super-chiaro su cosa sta verificando e la logica è corretta.

    
risposta data 25.11.2015 - 14:43
fonte
27

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

Informazioni sull'uso di isDigit() : dipende. Stai scrivendo un parser JSON? Solo 0 a 9 sono accettati come cifre, quindi non utilizzare isDigit() , verificare >= '0' e <= '9' . Stai elaborando l'input dell'utente? Usa isDigit() finché il resto del codice in realtà è in grado di gestire la stringa e trasformarla correttamente in un numero.

    
risposta data 25.11.2015 - 18:23
fonte
12

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

Devi distinguere tra il controllo di un carattere specifico o il controllo di un intervallo o di una classe di caratteri.

1) Verifica di un carattere specifico.

Per caratteri ordinari, usa il carattere letterale, ad esempio if(ch=='z')... . Se controlli contro caratteri speciali come tabulazione o interruzione di riga, dovresti usare gli escape, come if (ch=='\n')... . Se il personaggio che stai cercando è inusuale (ad esempio, non immediatamente riconoscibile o non disponibile su una tastiera standard), potresti usare un codice di carattere esadecimale piuttosto che il carattere letterale. Ma dal momento che un codice esadecimale è un "valore magico", lo si estrae su una costante e lo si documenta:

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

I codici esadecimali sono il modo standard per specificare i codici carattere.

2) Verifica di una classe o intervallo di caratteri

In realtà non dovresti farlo direttamente nel codice dell'applicazione, ma dovresti incapsularlo in una classe separata interessata solo alla classificazione dei caratteri. E dovresti essere diverso da questo, dal momento che le librerie esistono già per questo scopo e la classificazione dei caratteri è solitamente più complessa di quanto pensi, almeno se consideri caratteri al di fuori dell'intervallo ASCII.

Se ti preoccupi solo dei personaggi nell'intervallo ASCII, potresti usare letterali di caratteri in questa libreria, altrimenti probabilmente useresti letterali esadecimali. Se si guarda il codice sorgente per la libreria di caratteri incorporati Java, fa riferimento anche ai valori dei caratteri e agli intervalli utilizzando l'esadecimale, poiché questo è il modo in cui sono specificati nello standard Unicode.

    
risposta data 26.11.2015 - 09:49
fonte
-4

È sempre meglio usare c >= '0' perché per c >= 48 devi convertire c nel codice ASCII.

    
risposta data 02.12.2015 - 04:52
fonte
-5

Espressioni regolari ( RegEx s) hanno una classe di caratteri specifica per le cifre - \d - che può essere usata per rimuovere qualsiasi altra personaggio dalla tua 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 RegEx sono computazionalmente più impegnativi rispetto alle altre soluzioni proposte quindi non dovrebbero essere generalmente preferiti .

    
risposta data 27.11.2015 - 16:02
fonte

Leggi altre domande sui tag