web-dev-qa-db-fra.com

Chaîne à Int en Java - Probablement de mauvaises données, nécessité d'éviter les exceptions

Considérant que Java ne comporte pas de types nullable, ni de TryParse (), Comment gérez-vous la validation des entrées sans générer d'exceptions?

La manière habituelle:

String userdata = /*value from gui*/
int val;
try
{
   val = Integer.parseInt(userdata);
}
catch (NumberFormatException nfe)
{
   // bad data - set to sentinel
   val = Integer.MIN_VALUE;
}

Je pourrais utiliser une regex pour vérifier si c'est analysable, mais cela me semble aussi beaucoup de frais généraux.

Quelle est la meilleure pratique pour gérer cette situation?

EDIT: Justification: On a beaucoup parlé de la gestion des exceptions au sein de SO, et l’attitude générale est que les exceptions doivent être utilisées uniquement pour des scénarios inattendus. Cependant, je pense que de mauvaises entrées de l'utilisateur sont attendues, pas rares. Oui, c'est vraiment un point académique.

Autres modifications: 

Certaines réponses montrent exactement ce qui ne va pas avec SO. Vous ignorez la question posée et répondez à une autre question qui n’a rien à voir avec cela. La question ne concerne pas la transition entre les couches. La question ne demande pas quoi retourner si le nombre est incompréhensible. Pour tout ce que vous savez, val = Integer.MIN_VALUE; est exactement la bonne option pour l’application à partir de laquelle cet extrait de code totalement dépourvu de contexte a été pris.

37
Chris Cudmore

C'est à peu près tout, bien que renvoyer MIN_VALUE soit douteux, à moins que vous ne soyez sûr que ce soit la bonne chose à utiliser pour ce que vous utilisez essentiellement comme code d'erreur. Au moins, je documenterais le comportement du code d'erreur, cependant.

Cela peut également être utile (selon l’application) pour consigner la mauvaise entrée afin que vous puissiez tracer.

16
Steve B.

J'ai demandé s'il y avait des bibliothèques d'utilitaires open source qui avaient des méthodes pour effectuer cette analyse pour vous et la réponse est oui!

De Apache Commons Lang vous pouvez utiliser NumberUtils.toInt :

// returns defaultValue if the string cannot be parsed.
int i = org.Apache.commons.lang.math.NumberUtils.toInt(s, defaultValue);

De Google Guava vous pouvez utiliser Ints.tryParse :

// returns null if the string cannot be parsed
// Will throw a NullPointerException if the string is null
Integer i = com.google.common.primitives.Ints.tryParse(s);

Il n'est pas nécessaire d'écrire vos propres méthodes pour analyser les nombres sans générer d'exceptions.

27

Pour les données fournies par l'utilisateur, Integer.parseInt est généralement la mauvaise méthode car elle ne prend pas en charge l'internationalisation. Le package Java.text est votre ami (bavard).

try {
    NumberFormat format = NumberFormat.getIntegerInstance(locale);
    format.setParseIntegerOnly(true);
    format.setMaximumIntegerDigits(9);
    ParsePosition pos = new ParsePosition(0);
    int val = format.parse(str, pos).intValue();
    if (pos.getIndex() != str.length()) {
        // ... handle case of extraneous characters after digits ...
    }
    // ... use val ...
} catch (Java.text.ParseFormatException exc) {
    // ... handle this case appropriately ...
}
17

Quel est le problème avec votre approche? Je ne pense pas que cette façon de procéder nuise à la performance de votre application. C'est la bonne façon de le faire. Ne pas optimiser prématurément .

11
asterite

Je suis sûr que sa forme est mauvaise, mais j'ai une série de méthodes statiques sur une classe Utilities qui effectue des opérations telles que Utilities.tryParseInt(String value) qui renvoie 0 si String est unparseable et Utilities.tryParseInt(String value, int defaultValue) qui permet de spécifier une valeur à utiliser si parseInt() lève une exception. .

Je crois qu’il est parfois acceptable de renvoyer une valeur connue sur une mauvaise entrée. Un exemple très artificiel: vous demandez à l'utilisateur une date au format AAAAMMJJ et ils vous donnent une mauvaise entrée. Il peut être parfaitement acceptable de faire quelque chose comme Utilities.tryParseInt(date, 19000101) ou Utilities.tryParseInt(date, 29991231); en fonction des exigences du programme.

6
Grant Wagner

Je vais répéter ce que stinkyminky faisait valoir vers le bas du post:

Une approche généralement bien acceptée pour valider la saisie de l'utilisateur (ou la saisie à partir de fichiers de configuration, etc.) consiste à utiliser la validation avant le traitement effectif des données. Dans la plupart cas, il s'agit d'un bon choix de conception, même si cela peut entraîner plusieurs appels à des algorithmes d'analyse syntaxique.

Une fois que vous savez que vous avez correctement validé l’entrée utilisateur, alors il est prudent de l’analyser et d’ignorer, de consigner ou de convertir en RuntimeException l’Exception NumberFormatException.

Notez que cette approche nécessite que vous considériez votre modèle en deux parties: le modèle commercial (où nous nous soucions réellement d'avoir des valeurs au format int ou float) et le modèle d'interface utilisateur (où nous souhaitons réellement permettre à l'utilisateur de saisir ce qu'il veut. vouloir).

Pour que les données migrent du modèle d'interface utilisateur vers le modèle métier, elles doivent passer par une étape de validation (cela peut se produire champ par champ, mais la plupart des scénarios nécessitent une validation sur l'objet entier en cours de configuration). .

Si la validation échoue, l'utilisateur reçoit un retour d'information l'informant de ce qu'il a mal fait et la possibilité de le réparer.

Les bibliothèques de liaisons telles que JGoodies Binding et JSR 295 rendent ce genre de choses beaucoup plus facile à mettre en œuvre que cela puisse paraître. De nombreux frameworks Web fournissent des constructions qui séparent les entrées utilisateur du modèle métier actuel.

Pour ce qui est de la validation des fichiers de configuration (l’autre cas d’utilisation présenté dans certains commentaires), spécifier un défaut si une valeur particulière n’est pas spécifiée - mais si les données ne sont pas correctement formatées ( oh 'au lieu d'un' zéro '- ou ils ont copié à partir de MS Word et tous les back-ticks ont un caractère unicode génial), alors une sorte de retour d'information du système est nécessaire (même s'il échoue l'application en lançant une exception d'exécution) .

3
Kevin Day

Voici comment je le fais:

public Integer parseInt(String data) {
  Integer val = null;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}

Ensuite, le signal nul renvoie des données non valides. Si vous voulez une valeur par défaut, vous pouvez la changer en:

public Integer parseInt(String data,int default) {
  Integer val = default;
  try {
    val = Integer.parseInt(userdata);
  } catch (NumberFormatException nfe) { }
  return val;
}
2
noah

Essayez org.Apache.commons.lang.math.NumberUtils.createInteger(String s). Cela m'a beaucoup aidé. Il existe des méthodes similaires pour les doubles, les longs, etc.

1
Zlosny

Je pense que la meilleure pratique est le code que vous affichez.

Je ne choisirais pas l'alternative regex à cause des frais généraux.

1
Shimi Bandiel

Sémantique de nettoyage (Java 8 OptionalInt)

Pour Java 8+, j'utiliserais probablement RegEx pour pré-filtrer (pour éviter l'exception comme vous l'avez noté), puis encapsuler le résultat dans une primitive facultative (pour traiter le problème "par défaut"):

public static OptionalInt toInt(final String input) {
    return input.matches("[+-]?\\d+") 
            ? OptionalInt.of(Integer.parseInt(input)) 
            : OptionalInt.empty();
}

Si vous avez plusieurs entrées String, vous pouvez envisager de renvoyer une IntStream au lieu de OptionalInt afin que vous puissiez flatMap().

Références

0
AjahnCharles

Vous pouvez utiliser un entier, qui peut être défini sur null si vous avez une mauvaise valeur. Si vous utilisez Java 1.6, il fournira la boxing/unboxing automatique pour vous.

0
Milhous