Est-il préférable de vérifier `c> = '0'` ou` c> = 48`?

46

Après une discussion avec certains de mes collègues, j'ai une question «philosophique» sur la manière de traiter le type de données char en Java, en suivant les meilleures pratiques.

Supposons un scénario simple (bien entendu, ce n’est qu’un exemple très simple pour donner un sens à ma question) où, en prenant un String, vous devez compter le nombre de caractères numériques qu’il contient.

Ce sont les 2 solutions possibles:

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

Lequel des deux est le plus «propre» et conforme aux meilleures pratiques Java?

wyr0
la source
141
Pourquoi voudriez-vous écrire 48 et 57 alors que vous voulez dire "0" et "9"? Ecrivez juste ce que vous voulez dire.
Brandin
9
Attendez, que faites-vous, Java a les VK_constantes que vous êtes censé utiliser, deuxièmement, utiliser des codes de caractères est meilleur que des caractères. @Brandin C'est ce qu'on appelle les pratiques de codage
Martin Barker
12
Sans prendre la peine de faire plus que juger les 6 personnes qui pensaient que c’était une bonne question. Utilisez-vous des caractères comme des nombres? Si c'est le cas, utilisez des chiffres. Est-ce que vous l'utilisez comme lettres? Si c'est le cas, utilisez des lettres.
Alec Teal
17
@MartinBarker Les VK_*constantes correspondent à des clés et non à des caractères .
CodesInChaos
2
Il m'a fallu quelques minutes pour déterminer ce que ce code fait par rapport à votre question. Déjà, ce n'est pas clair car cela suppose que je sache en (1) que je sais que c'est la plage de chiffres d'ISO-Latin 1. Cela pose donc un problème du point de vue de la maintenance.
CyberSkull

Réponses:

124

Les deux sont horribles, mais le premier est plus horrible.

Les deux ignorent la capacité intégrée de Java à décider quels caractères sont "numériques" (via les méthodes entrées Character). Mais la première ne tient pas compte non seulement la nature Unicode des chaînes, en supposant qu'il ne peut y avoir qu'une 0123456789, elle aussi obscurcit même ce raisonnement invalide en utilisant des codes de caractères qui font sens que si vous savez quelque chose sur l'histoire de codages de caractères.

Kilian Foth
la source
33
Pourquoi supposez-vous que les chiffres non-rejetés non-ASCII sont faux? Cela dépend du contexte.
CodesInChaos
21
@CodesInChaos Si vous voulez vraiment trouver des caractères numériques , la recherche de 0123456789 est tout simplement fausse. Si vous souhaitez réellement rechercher uniquement ces dix caractères, il s’agit essentiellement de jetons dépourvus de sens qui paraissent familiers par inadvertance aux personnes ne connaissant que l’ASCII / ISO-Latin. Il n’ya rien de mal à cela - je dois souvent le faire précisément, par exemple, pour interagir avec un logiciel existant qui n’accepte réellement que ces dix caractères. Mais ensuite, vous devriez préciser vos intentions en utilisant quelque chose du genre matches("[0-9]+"), plutôt que d'exploiter le tour de passe motivé par l'histoire.
Kilian Foth
15
Il existe des chiffres pleine largeur , qui ressemblent aux chiffres ASCII. De nombreux logiciels sont généralement nécessaires pour les accepter à la place des chiffres ASCII. (Évidemment, beaucoup de logiciels sont cassés, en fonction de la définition de "beaucoup de". Vous pouvez facilement le constater car les éditeurs de logiciels d’un pays ne peuvent pas vendre dans un autre pays car ils ne respectent pas les exigences des autres pays. )
Rwong
37
A.
BlueRaja - Danny Pflughoeft
14
"Les deux sont horribles", mais vous avez oublié de dire la bonne solution ;-)
Kromster dit de soutenir Monica
163

Ni. Laissez la classe de caractères intégrée de Java le découvrir pour vous.

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

Il y a un peu plus de plages de caractères que les chiffres ASCII qui comptent comme chiffres, et aucun exemple que vous avez posté ne les comptera. Le JavaDoc pour Character.isDigit()répertorie ces plages de caractères comme étant des chiffres valides:

Certaines plages de caractères Unicode contenant des chiffres:

  • "\ u0030" à "\ u0039", chiffres ISO-LATIN-1 ("0" à "9")
  • '\ u0660' à '\ u0669', chiffres arabe-indien
  • '\ u06F0' à '\ u06F9', chiffres étendus arabe-indien
  • '\ u0966' à '\ u096F', chiffres en devanagari
  • '\ uFF10' à '\ uFF19', chiffres pleine largeur

De nombreuses autres plages de caractères contiennent également des chiffres.

Cela étant dit, il convient de déléguer à Character.isDigit()même avec cette liste. Au fur et à mesure que les nouveaux plans Unicode sont remplis, le code Java sera mis à jour. La mise à niveau de la machine virtuelle Java pourrait permettre à l'ancien code de fonctionner de manière transparente avec les nouveaux caractères numériques. Il est également sec : en localisant le code «s’agit-il d’un chiffre» à un endroit référencé ailleurs, les aspects négatifs de la duplication de code (c.-à-d. Les bugs) peuvent être évités. Enfin, notez la dernière ligne: cette liste n’est pas exhaustive et il existe d’autres chiffres.

Personnellement, je préférerais déléguer aux principales bibliothèques Java et consacrer mon temps à des tâches plus productives qu'à "déterminer ce qu'est un chiffre".


La seule exception à cette règle concerne les cas où vous devez réellement tester les chiffres ASCII littéraux et non les autres chiffres. Par exemple, si vous analysez un flux et que seuls les chiffres ASCII (par opposition aux autres chiffres) ont une signification particulière, il ne serait pas approprié de les utiliser Character.isDigit().

Dans ce cas, j’écrirais une autre méthode, par exemple, MyClass.isAsciiDigit()et jetterais la logique. Vous obtenez les mêmes avantages de la réutilisation du code, le nom est très clair quant à ce qu’il vérifie, et la logique est correcte.


la source
4
Excellente réponse pour réellement fournir le code propre qui fait le tour.
Pierre Arlaud
27

Si vous écrivez une application en C qui utilise EBCDIC comme jeu de caractères de base et doit traiter des caractères ASCII, utilisez 48et 57. Est-ce que tu fais ça? Je ne pense pas.

À propos de l'utilisation isDigit(): cela dépend. Vous écrivez un analyseur JSON? Seulement 0pour 9sont acceptées sous forme de chiffres, ne l' utilisez pas isDigit(), vérifiez >= '0'et <= '9'. Traitez-vous les entrées utilisateur? Utilisez-le isDigit()tant que le reste de votre code peut gérer la chaîne et la transformer en nombre correctement.

gnasher729
la source
3
En réalité, vous pouvez écrire des applications en Java qui récupèrent et renvoient EBCDIC. Ce n'est pas amusant
Thorbjørn Ravn Andersen
Le même 'pas amusant' parcourait du code écrit en utilisant les valeurs décimales des caractères EBCDIC lors de la conversion en un environnement multiplate-forme ...
Gwyn Evans
1
Si vous traitez des données EBCDIC en Java, vous devriez probablement les convertir en jeu de caractères UTF-16 natif Java avant de les traiter sous forme de caractères. Mais je suppose que cela dépend vraiment de l'application. Espérons que si votre programme doit traiter avec EBCDIC, alors vous comprendrez ce qui doit être fait.
Michael Burr
1
Le point principal est que pour le traitement EBCDIC en Java, "0" et 48 ont tort de détecter un chiffre zéro. Plus courant, en C, C ++, etc. '\ n' et '\ r' sont définis par l'implémentation. Par conséquent, si vous souhaitez détecter une paire Windows CR / LF dans un fichier à l'aide d'un compilateur autre que Windows, vérifiez plutôt les valeurs décimales. vérifier pour '\ n' et '\ r'.
gnasher729
12

Le deuxième exemple est clairement supérieur. La signification du deuxième exemple est immédiatement évidente lorsque vous regardez le code. La signification du premier exemple n’est évidente que si vous avez mémorisé l’intégralité de la table ASCII dans votre tête.

Vous devez faire la distinction entre rechercher un caractère spécifique ou rechercher une plage ou une classe de caractères.

1) Vérifier un caractère spécifique.

Pour les caractères ordinaires, utilisez le littéral de caractère, par exemple, if(ch=='z').... Si vous comparez des caractères spéciaux tels que tabulation ou saut de ligne, vous devez utiliser les échappements, comme if (ch=='\n').... Si le caractère que vous recherchez est inhabituel (par exemple, non immédiatement reconnaissable ou indisponible sur un clavier standard), vous pouvez utiliser un code de caractère hexadécimal plutôt que le caractère littéral. Mais comme un code hexadécimal est une "valeur magique", vous devez l'extraire en une constante et la documenter:

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

Les codes hexadécimaux constituent la méthode standard de spécification des codes de caractères.

2) Vérifier une classe de caractères ou une plage

Vous ne devriez vraiment pas faire cela directement dans le code de l'application, mais l'encapsuler dans une classe séparée uniquement concernée par la classification des caractères. Et vous devriez varier ce nombre, car des bibliothèques existent déjà à cet effet, et la classification des caractères est généralement plus complexe que vous ne le pensez, du moins si vous considérez des caractères en dehors de la plage ASCII.

Si vous ne vous souciez que des caractères de la plage ASCII, vous pouvez utiliser des littéraux de caractères dans cette bibliothèque, sinon vous utiliseriez probablement des littéraux hexadécimaux. Si vous examinez le code source de la bibliothèque de caractères intégrée Java, il fait également référence aux valeurs et aux plages de caractères utilisant hexadécimal, car elles sont spécifiées dans la norme Unicode.

JacquesB
la source
1
Je vous recommanderais également d'écrire le caractère littéral au format hexadécimal en '\x2603'indiquant plutôt que vous testez la valeur d'un caractère avec un codage hexadécimal et non pas n'importe quel nombre aléatoire.
wefwefa3
-4

Il est toujours préférable d'utiliser c >= '0'car pour c >= 48vous devez convertir c en code ASCII.

Prem Patel
la source
3
Qu'est-ce que cette réponse dit qui n'a pas déjà été dit dans les réponses précédentes d'il y a une semaine?
-5

Les expressions régulières ( RegEx ) ont une classe de caractères spécifique pour les chiffres - \d- pouvant être utilisée pour supprimer tout autre caractère de votre chaîne. La longueur de la chaîne résultante est la valeur souhaitée.

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

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

Notez, cependant, que les RegEx sont plus exigeants en calcul que les autres solutions proposées et qu’ils ne devraient donc pas être préférés .

Stefano Bragaglia
la source
Manière très élégante de faire le contrôle!
Kevin Robatel
Les expressions rationnelles sont exagérées pour une tâche comme celle-ci
Pharap
2
@ StefanoBragaglia Après avoir relu votre réponse, je pense que cela ne répond pas vraiment à la question.
Pharap
2
Votre réponse fournit une façon différente de résoudre le problème de "comment compter les chiffres dans une chaîne". Il ne répond pas au problème sous-jacent avec les exemples de code et la représentation des constantes - sous forme de chiffres ou de caractères.
2
Cela ne compte pas réellement les chiffres (il vous indique simplement quelle est la longueur de la chaîne après avoir supprimé tous les chiffres, ce qui n'est ni ici ni là), mais je conviens que cela ne répond pas vraiment à la question. Comme par exemple, personne ne demandait de supprimer des caractères des chaînes. La question ne fait que demander quel est le meilleur moyen de vérifier si le caractère d'un personnage est numérique.
doppelgreener