Après une discussion avec certains de mes collègues, j'ai une question "philosophique" sur la façon de traiter le type de données char en Java, en suivant les meilleures pratiques.
Supposons un scénario simple (évidemment ce n'est qu'un exemple très simple pour donner un sens à ma question) où, étant donné une chaîne en entrée, vous devez compter le nombre de chiffres personnages présents en elle.
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 plus "propre" et conforme aux meilleures pratiques Java?
Les deux sont horribles, mais le premier est plus horrible.
Les deux ignorent la capacité intégrée de Java pour décider quels caractères sont "numériques" (via les méthodes dans Character
). Mais le premier ignore non seulement la nature Unicode des chaînes, en supposant qu'il ne peut y avoir que 0123456789, il aussi obscurcit même ce raisonnement invalide en utilisant des codes de caractères qui n'ont de sens que si vous savez quelque chose sur l'histoire d'encodages de caractères.
Ni. Laissez la classe intégrée Character de Java le comprendre pour vous.
for (int i = 0; i < s.length(); ++i) {
if (Character.isDigit(s.charAt(i))) {
++n;
}
}
Il existe quelques plages de caractères de plus que les chiffres ASCII qui comptent comme des chiffres, et aucun des exemples que vous avez publiés ne les comptera. Le JavaDoc pour Character.isDigit()
répertorie ces plages de caractères comme étant des chiffres valides:
Quelques plages de caractères Unicode qui contiennent des chiffres:
- '\ u0030' à '\ u0039', chiffres ISO-LATIN-1 ('0' à '9')
- '\ u0660' à '\ u0669', chiffres arabes
- '\ u06F0' à '\ u06F9', chiffres arabes étendus
- '\ u0966' à '\ u096F', chiffres Devanagari
- '\ uFF10' à '\ uFF19', chiffres pleine largeur
De nombreuses autres plages de caractères contiennent également des chiffres.
Cela étant dit, il faut déléguer à Character.isDigit()
même avec cette liste. Au fur et à mesure que de nouveaux avions Unicode sont peuplés, le code Java sera mis à jour. La mise à niveau de la JVM pourrait faire fonctionner l'ancien code avec de nouveaux caractères numériques de manière transparente. Il est également SEC : en localisant le code "est-ce un chiffre" à un endroit référencé ailleurs, les aspects négatifs de la duplication de code (c.-à-d. les bogues) peuvent être évités. Enfin, notez la dernière ligne: cette liste n'est pas exhaustif, et il existe d'autres chiffres.
Personnellement, je préfère déléguer aux bibliothèques de base Java et consacrer mon temps à des tâches plus productives que de "déterminer ce qu'est un chiffre".
La seule exception à cette règle est si vous avez vraiment besoin de tester les littéraux ASCII chiffres et pas autres chiffres. Par exemple, si vous analysez un flux et que uniquement ASCII chiffres (par opposition aux autres chiffres) ont une signification spéciale, alors ce serait pas convient d'utiliser Character.isDigit()
.
Dans ce cas, j'écrirais une autre méthode, par ex. MyClass.isAsciiDigit()
et y mettre la logique. Vous bénéficiez des 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.
Si vous écrivez une application en C qui utilise EBCDIC comme jeu de caractères de base et doit traiter ASCII caractères, utilisez alors 48
Et 57
. Faites-vous Je ne pense pas.
À propos de l'utilisation de isDigit()
: cela dépend. Ecrivez-vous un analyseur JSON? Seuls 0
À 9
Sont acceptés comme chiffres, donc n'utilisez pas isDigit()
, recherchez >= '0'
Et <= '9'
. Traitez-vous les entrées des utilisateurs? Utilisez isDigit()
tant que le reste de votre code peut réellement gérer la chaîne et la transformer correctement en nombre.
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é du tableau ASCII dans votre tête).
Vous devez faire la distinction entre la vérification d'un caractère spécifique ou la vérification d'une plage ou d'une classe de caractères.
1) Recherche d'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 comme des tabulations ou des sauts 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 non disponible sur un clavier standard), vous pouvez utiliser un code 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 sont la manière standard de spécifier les codes de caractères.
2) Vérification d'une classe ou d'une plage de caractères
Vous ne devriez vraiment pas faire cela directement dans le code d'application, mais vous devez l'encapsuler dans une classe distincte uniquement concernée par la classification des caractères. Et vous devriez être différent de cela, 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 les caractères en dehors de la plage ASCII.
Si vous ne vous préoccupez que des caractères de la plage ASCII, vous pouvez utiliser des littéraux de caractères dans cette bibliothèque, sinon vous utiliserez probablement des littéraux hexadécimaux. Si vous regardez le code source de la = Java bibliothèque de caractères intégrée, il fait également référence aux valeurs et aux plages de caractères en hexadécimal, car c'est ainsi qu'elles sont spécifiées dans la norme Unicode.