Ist es besser, "c> =" 0 "oder" c> = 48 "zu überprüfen?

46

Nach einer Diskussion mit einigen meiner Kollegen habe ich eine „philosophische“ Frage dazu, wie der char-Datentyp in Java gemäß den Best Practices behandelt wird.

Angenommen, ein einfaches Szenario (dies ist offensichtlich nur ein sehr einfaches Beispiel, um meiner Frage eine Übungsbedeutung zu geben) , bei dem Sie bei Eingabe eines Strings die Anzahl der darin enthaltenen numerischen Zeichen zählen müssen.

Dies sind die 2 möglichen Lösungen:

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++;
        }
    }

Welche der beiden Methoden ist "sauberer" und entspricht den bewährten Java-Methoden?

wyr0
quelle
141
Warum würden Sie 48 und 57 schreiben, wenn Sie tatsächlich '0' und '9' meinen? Schreiben Sie einfach, was Sie meinen.
Brandin
9
Warten Sie, was Sie tun. Java hat die VK_Konstanten, die Sie verwenden sollen. Zweitens ist die Verwendung von Zeichencodes besser als die Verwendung von Zeichen. @Brandin Es heißt Codierungspraktiken
Martin Barker
12
Ohne sich die Mühe zu machen, mehr als die 6 Leute zu beurteilen, die das für eine gute Frage hielten. Verwenden Sie Zeichen als Zahlen? Wenn ja, verwenden Sie Zahlen. Benutzt du es als Buchstaben? Verwenden Sie in diesem Fall Buchstaben.
Alec Teal
17
@MartinBarker Die VK_*Konstanten entsprechen Schlüsseln, nicht Zeichen .
CodesInChaos
2
Ich habe einige Minuten gebraucht, um festzustellen, was dieser Code in Bezug auf Ihre Frage bewirkt. Es ist bereits nicht klar, da ich in (1) voraussetze, dass ich weiß, dass dies der Ziffernbereich von ISO-Latin 1 ist. Dies macht es also unter dem Gesichtspunkt der Wartung problematisch.
CyberSkull

Antworten:

124

Beide sind schrecklich, aber der erste ist schrecklicher.

Beide ignorieren Javas eingebaute Fähigkeit, zu entscheiden, welche Zeichen "numerisch" sind (über Methoden in Character). Aber der erste nicht ignoriert nur die Unicode - Natur von Strings, unter der Annahme , dass es nur 0123456789 sein kann, ist es auch verschleiert auch diese ungültig Argumentation von Zeichencodes verwenden , die Sinn machen nur , wenn Sie etwas über die Geschichte der Zeichenkodierungen kennen.

Kilian Foth
quelle
33
Warum gehen Sie davon aus, dass das Nicht-Zurückweisen von Nicht-ASCII-Ziffern falsch ist? Das hängt vom Kontext ab.
CodesInChaos
21
@CodesInChaos Wenn Sie wirklich nach numerischen Zeichen suchen möchten , ist das Scannen nach 0123456789 einfach falsch. Wenn Sie tatsächlich nur nach diesen zehn Zeichen suchen möchten, handelt es sich im Grunde genommen um bedeutungslose Token, die nur für Personen, die nur ASCII / ISO-Latein kennen, aus Versehen bekannt sind. Daran ist nichts auszusetzen - ich muss oft genau das tun, um mit Legacy-Software zu interagieren, die wirklich nur diese zehn Zeichen akzeptiert. Aber dann sollten Sie Ihre Absichten klarstellen, indem Sie so etwas wie verwenden matches("[0-9]+"), anstatt den historisch motivierten Bereichstrick auszunutzen.
Kilian Foth
15
Es gibt Ziffern mit voller Breite , die den ASCII-Ziffern entsprechen, und im Allgemeinen ist viel Software erforderlich, um sie anstelle von ASCII-Ziffern zu akzeptieren. (Offensichtlich ist eine Menge Software kaputt, abhängig von der Definition von "viel". Sie können leicht feststellen, dass Softwareanbieter in einem Land es unmöglich finden, in ein anderes Land zu verkaufen, da die Anbieter die Anforderungen der anderen Länder nicht erfüllen. )
rwong
37
I I have a Japanese installed , and accidentally type in
BlueRaja - Danny Pflughoeft
14
"Beide sind schrecklich", aber du hast vergessen, die richtige Lösung zu sagen
;-)
163

Weder. Lassen Sie sich von Javas eingebauter Charakterklasse helfen.

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

Es gibt ein paar mehr Zeichenbereiche als die ASCII-Ziffern, die als Ziffern gelten, und keines der von Ihnen veröffentlichten Beispiele zählt sie. Die JavaDoc für Character.isDigit()Listen dieser Zeichenbereiche als gültige Ziffern zu sein:

Einige Unicode-Zeichenbereiche, die Ziffern enthalten:

  • '\ u0030' bis '\ u0039', ISO-LATIN-1-Ziffern ('0' bis '9')
  • '\ u0660' bis '\ u0669', arabisch-indische Ziffern
  • '\ u06F0' bis '\ u06F9', erweiterte arabisch-indische Ziffern
  • '\ u0966' bis '\ u096F', Devanagari-Ziffern
  • '\ uFF10' bis '\ uFF19', Ziffern voller Breite

Viele andere Zeichenbereiche enthalten auch Ziffern.

Character.isDigit()Davon abgesehen sollte man auch mit dieser Liste delegieren . Wenn neue Unicode-Ebenen gefüllt werden, wird der Java-Code aktualisiert. Ein Upgrade der JVM könnte dazu führen, dass alter Code nahtlos mit neuen Ziffern funktioniert. Es ist auch TROCKEN : Durch Lokalisieren des Codes "Ist dies eine Ziffer?" An einer Stelle, auf die an anderer Stelle verwiesen wird, können die negativen Aspekte der Codeduplizierung (dh Fehler) vermieden werden. Beachten Sie zum Schluss die letzte Zeile: Diese Liste ist nicht vollständig, und es gibt andere Ziffern.

Persönlich würde ich lieber an die Java-Kernbibliotheken delegieren und meine Zeit für produktivere Aufgaben verwenden, als "herauszufinden, was eine Ziffer ist".


Die einzige Ausnahme von dieser Regel ist, wenn Sie wirklich auf die wörtlichen ASCII-Ziffern und nicht auf andere Ziffern testen müssen . Wenn Sie beispielsweise einen Stream analysieren und nur ASCII-Ziffern (im Gegensatz zu anderen Ziffern) eine spezielle Bedeutung haben, ist die Verwendung nicht sinnvoll Character.isDigit().

In diesem Fall würde ich z. B. eine andere Methode schreiben MyClass.isAsciiDigit()und die Logik dort einfügen. Sie erhalten die gleichen Vorteile der Wiederverwendung von Code, der Name ist klar, was überprüft wird, und die Logik ist korrekt.


quelle
4
Tolle Antwort, um tatsächlich den sauberen Code bereitzustellen, der den Trick macht.
Pierre Arlaud
27

Wenn Sie jemals eine Anwendung in C schreiben, die EBCDIC als Basiszeichensatz verwendet und ASCII-Zeichen verarbeiten muss, verwenden Sie 48und 57. Machst du das Ich glaube nicht

Über die Verwendung isDigit(): es kommt darauf an. Schreiben Sie einen JSON-Parser? Nur 0um 9als Ziffern akzeptiert zu werden, also nicht verwenden isDigit(), auf >= '0'und prüfen <= '9'. Verarbeiten Sie Benutzereingaben? Verwenden Sie isDigit(), solange der Rest Ihres Codes tatsächlich mit der Zeichenfolge umgehen und sie korrekt in eine Zahl umwandeln kann.

gnasher729
quelle
3
Tatsächlich können Sie Anwendungen in Java schreiben, die EBCDIC abrufen und zurückgeben. Das macht keinen Spaß.
Thorbjørn Ravn Andersen
Auf ähnliche Weise hat "kein Spaß" Code durchlaufen, der mit den Dezimalwerten der EBCDIC-Zeichen geschrieben wurde, als er in eine plattformübergreifende Umgebung konvertiert wurde ...
Gwyn Evans,
1
Wenn Sie EBCDIC-Daten in Java verarbeiten, sollten Sie sie wahrscheinlich in den Java-Zeichensatz UTF-16 konvertieren, bevor Sie sie als Zeichen verarbeiten. Aber ich denke, das hängt wirklich von der Anwendung ab. Hoffentlich verstehen Sie, was zu tun ist, wenn Ihr Programm mit EBCDIC zu tun hat.
Michael Burr
1
Der Hauptpunkt ist, dass für die Verarbeitung von EBCDIC in Java sowohl '0' als auch 48 falsch sind , um eine Ziffer Null zu erkennen. In C, C ++ usw. ist die Implementierung aktueller. '\ N' und '\ r' sind definiert. Wenn Sie also ein Windows CR / LF-Paar in einer Datei mithilfe eines Nicht-Windows-Compilers erkennen möchten, überprüfen Sie besser die Dezimalwerte als Suche nach '\ n' und '\ r'.
gnasher729
12

Das zweite Beispiel ist eindeutig überlegen. Die Bedeutung des zweiten Beispiels wird beim Betrachten des Codes sofort deutlich. Die Bedeutung des ersten Beispiels ist nur dann offensichtlich, wenn Sie die gesamte ASCII-Tabelle in Ihrem Kopf gespeichert haben.

Sie sollten zwischen der Suche nach einem bestimmten Zeichen und der Suche nach einem Bereich oder einer Klasse von Zeichen unterscheiden.

1) Überprüfung auf ein bestimmtes Zeichen.

Verwenden Sie für gewöhnliche Zeichen das Zeichenliteral, z if(ch=='z').... Wenn Sie nach Sonderzeichen wie Tabulator oder Zeilenumbruch suchen, sollten Sie die Escape-Zeichen wie verwenden if (ch=='\n').... Wenn das Zeichen, nach dem Sie suchen, ungewöhnlich ist (z. B. auf einer Standardtastatur nicht sofort erkennbar oder nicht verfügbar), verwenden Sie möglicherweise einen Hex-Zeichencode anstelle des Literalzeichens. Da ein Hex-Code ein "magischer Wert" ist, würden Sie ihn in eine Konstante extrahieren und dokumentieren:

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

Hex-Codes sind die Standardmethode zur Angabe von Zeichencodes.

2) Prüfen auf eine Zeichenklasse oder einen Zeichenbereich

Sie sollten dies wirklich nicht direkt im Anwendungscode tun, sondern in einer separaten Klasse einschließen, die sich nur mit der Klassifizierung von Zeichen befasst. Und Sie sollten hiervon abweichen, da für diesen Zweck bereits Bibliotheken existieren und die Klassifizierung von Zeichen in der Regel komplexer ist als Sie denken, zumindest wenn Sie Zeichen außerhalb des ASCII-Bereichs berücksichtigen.

Wenn Sie sich nur um Zeichen im ASCII-Bereich kümmern, können Sie in dieser Bibliothek Zeichenliterale verwenden, andernfalls würden Sie wahrscheinlich Hex-Literale verwenden. Wenn Sie sich den Quellcode für die in Java integrierte Zeichenbibliothek ansehen, bezieht sich dieser auch auf Zeichenwerte und -bereiche mit Hexadezimalzeichen, da diese im Unicode-Standard so angegeben sind.

JacquesB
quelle
1
Ich würde auch empfehlen, das Zeichenliteral in '\x2603'hexadezimaler Schreibweise zu schreiben, um zu verdeutlichen, dass Sie den Wert für ein Zeichen mit hexadezimaler Codierung und nicht nur mit einer Zufallszahl testen.
Wefwefa3
-4

Es ist immer besser zu verwenden, c >= '0'da c >= 48Sie c in ASCII-Code konvertieren müssen.

Prem Patel
quelle
3
Was bedeutet diese Antwort, die in den vorherigen Antworten von vor einer Woche noch nicht gesagt wurde?
-5

Reguläre Ausdrücke ( RegEx s) haben eine bestimmte Zeichenklasse für Ziffern - \d-, mit der Sie beliebige andere Zeichen aus Ihrer Zeichenfolge entfernen können. Die Länge der resultierenden Zeichenfolge entspricht dem gewünschten Wert.

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

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

Beachten Sie jedoch, dass RegEx-Systeme rechenintensiver sind als die anderen vorgeschlagenen Lösungen, weshalb sie nicht generell bevorzugt werden sollten .

Stefano Bragaglia
quelle
Sehr elegante Art, den Check durchzuführen!
Kevin Robatel
Regexes sind Overkill für eine Aufgabe wie diese
Pharap
2
@StefanoBragaglia Nachdem ich deine Antwort noch einmal gelesen habe, denke ich, dass sie die Frage nicht wirklich beantwortet.
Pharap
2
Ihre Antwort bietet eine andere Möglichkeit, das Problem zu lösen: "Wie zähle ich Ziffern in einer Zeichenfolge?". Es beantwortet das zugrunde liegende Problem nicht mit den Codebeispielen und der Darstellung der Konstanten - entweder als Zahlen oder als Zeichen.
2
Dabei werden die Ziffern nicht gezählt (es wird nur die Länge der Zeichenfolge angegeben, nachdem Sie alle Ziffern entfernt haben, weder hier noch dort), aber ich stimme zu, dass die Frage nicht beantwortet wird. Als ob zum Beispiel niemand danach gefragt hätte, Zeichen aus Strings zu entfernen. Die Frage fragt nur nach der geeigneten Best-Practice-Methode, um zu überprüfen, ob ein Zeichen numerisch ist.
Doppelgreener