web-dev-qa-db-fra.com

Bonne façon d'encapsuler Integer.parseInt ()

J'ai un projet dans lequel nous utilisons souvent Integer.parseInt() pour convertir une chaîne en un entier. Lorsque quelque chose ne va pas (par exemple, la String n'est pas un nombre mais la lettre a, ou autre), cette méthode lève une exception. Cependant, si je dois gérer des exceptions dans mon code partout, cela commence à être très moche très rapidement. Je voudrais mettre cela dans une méthode, cependant, je n'ai aucune idée de la façon de retourner une valeur propre pour montrer que la conversion s'est mal passée. 

En C++, j'aurais pu créer une méthode qui accepte un pointeur sur un int et laisse la méthode elle-même renvoyer true ou false. Cependant, à ma connaissance, cela n’est pas possible en Java. Je pourrais aussi créer un objet contenant une variable true/false et la valeur convertie, mais cela ne semble pas idéal non plus. La même chose vaut pour une valeur globale, et cela pourrait me poser quelques problèmes avec le multithreading. 

Alors, y a-t-il un moyen propre de faire cela? 

79
Bad Horse

Vous pouvez renvoyer une Integer au lieu d'une int, en retournant null en cas d'échec de l'analyse.

C’est dommage que Java ne fournisse aucun moyen de le faire sans qu’une exception soit générée en interne; vous pouvez masquer l’exception (en l’attrapant et en lui renvoyant la valeur null), mais cela pourrait toujours être un problème de performances si vous analysez des centaines des milliers de bits de données fournies par l'utilisateur.

EDIT: Code pour une telle méthode:

public static Integer tryParse(String text) {
  try {
    return Integer.parseInt(text);
  } catch (NumberFormatException e) {
    return null;
  }
}

Notez que je ne suis pas sûr de savoir ce que cela va faire si text est null. Vous devez considérer que - s’il s’agit d’un bogue (c’est-à-dire que votre code peut transmettre une valeur non valide, mais ne doit jamais passer à null), le lancement d’une exception est approprié; si cela ne représente pas un bogue, vous devriez probablement simplement renvoyer null comme vous le feriez pour toute autre valeur non valide.

A l'origine, cette réponse utilisait le constructeur new Integer(String); il utilise maintenant Integer.parseInt et une opération de boxe; De cette façon, les petites valeurs finiront par être encadrées par des objets Integer mis en cache, ce qui le rendra plus efficace dans ces situations.

126
Jon Skeet

Quel comportement attendez-vous quand ce n'est pas un nombre?

Si, par exemple, vous avez souvent une valeur par défaut à utiliser lorsque l'entrée n'est pas un nombre, une méthode telle que celle-ci pourrait être utile:

public static int parseWithDefault(String number, int defaultVal) {
  try {
    return Integer.parseInt(number);
  } catch (NumberFormatException e) {
    return defaultVal;
  }
}

Des méthodes similaires peuvent être écrites pour différents comportements par défaut lorsque l'entrée ne peut pas être analysée.

32
Joachim Sauer

Dans certains cas, vous devez traiter les erreurs d'analyse comme des situations rapides, mais dans d'autres, comme la configuration de l'application, je préfère gérer les entrées manquantes avec les valeurs par défaut à l'aide de Apache Commons Lang 3 NumberUtils .

int port = NumberUtils.toInt(properties.getProperty("port"), 8080);
22
Bryan W. Wagner

Pour éviter la gestion des exceptions, utilisez une expression régulière afin de vous assurer que tous les chiffres sont en premier:

if(value.matches("\\d+") {
    Integer.parseInt(value);
}
15
Daddyboy

Il y a Ints.tryParse() dans Goyave . Il ne lève pas d'exception sur les chaînes non numériques, mais il lève l'exception sur les chaînes nulles 

10
husayt

Après avoir lu les réponses à la question, je pense qu’encapsuler ou encapsuler la méthode parseInt n’est pas nécessaire, peut-être même pas une bonne idée.

Vous pouvez retourner 'null' comme Jon l'a suggéré, mais cela remplace plus ou moins une construction try/catch par un test null. Si vous "oubliez" la gestion des erreurs, le comportement ne présente qu'une légère différence: si vous ne capturez pas l'exception, il n'y a pas d'affectation et la variable de gauche conserve l'ancienne valeur. Si vous ne testez pas la valeur null, vous serez probablement touché par la JVM (NPE).

la suggestion de yawn me semble plus élégante, car je n'aime pas retourner null pour signaler des erreurs ou des états exceptionnels. Maintenant, vous devez vérifier l’égalité référentielle avec un objet prédéfini, ce qui indique un problème. Mais, comme le soulignent d’autres, si vous «oubliez» de vérifier et qu’une chaîne est impardonnable, le programme continue avec l’entier encapsulé dans votre objet «ERROR» ou «NULL».

La solution de Nikolay est encore plus orientée objet et fonctionnera également avec les méthodes parseXXX d'autres classes de wrapper. Mais à la fin, il vient de remplacer l’exception NumberFormatException par une exception OperationNotSupported. À nouveau, vous avez besoin d’un essai/d’une capture pour gérer les entrées non analysables.

Donc, sa conclusion est de ne pas encapsuler la méthode plain parseInt. J'encapsulerais seulement si je pouvais aussi ajouter un peu de gestion des erreurs (dépendant de l'application).

4
Andreas_D

Peut-être que vous pouvez utiliser quelque chose comme ceci:

public class Test {
public interface Option<T> {
    T get();

    T getOrElse(T def);

    boolean hasValue();
}

final static class Some<T> implements Option<T> {

    private final T value;

    public Some(T value) {
        this.value = value;
    }

    @Override
    public T get() {
        return value;
    }

    @Override
    public T getOrElse(T def) {
        return value;
    }

    @Override
    public boolean hasValue() {
        return true;
    }
}

final static class None<T> implements Option<T> {

    @Override
    public T get() {
        throw new UnsupportedOperationException();
    }

    @Override
    public T getOrElse(T def) {
        return def;
    }

    @Override
    public boolean hasValue() {
        return false;
    }

}

public static Option<Integer> parseInt(String s) {
    Option<Integer> result = new None<Integer>();
    try {
        Integer value = Integer.parseInt(s);
        result = new Some<Integer>(value);
    } catch (NumberFormatException e) {
    }
    return result;
}

}
4
Nikolay Ivanov

Vous pouvez également répliquer le comportement C++ que vous souhaitez très simplement

public static boolean parseInt(String str, int[] byRef) {
    if(byRef==null) return false;
    try {
       byRef[0] = Integer.parseInt(prop);
       return true;
    } catch (NumberFormatException ex) {
       return false;
    }
}

Vous utiliseriez la méthode comme suit:

int[] byRef = new int[1];
boolean result = parseInt("123",byRef);

Après cela, la variable result est vraie si tout va bien et que byRef[0] contient la valeur analysée.

Personnellement, je me contenterais d'attraper l'exception.

2
Wilian Z.

Mon Java est un peu rouillé, mais voyons si je peux vous diriger dans la bonne direction:

public class Converter {

    public static Integer parseInt(String str) {
        Integer n = null;

        try {
            n = new Integer(Integer.tryParse(str));
        } catch (NumberFormatException ex) {
            // leave n null, the string is invalid
        }

        return n;
    }

}

Si votre valeur de retour est null, votre valeur est mauvaise. Sinon, vous avez une Integer valide.

1
Adam Maras

Si vous utilisez Java 8 ou une version ultérieure, vous pouvez utiliser une bibliothèque que je viens de publier: https://github.com/robtimus/try-parse . Il a un support pour int, long et boolean qui ne repose pas sur des exceptions capturantes. Contrairement à Ints.tryParse de Guava, il retourne OptionalInt/OptionalLong/Optional, un peu comme dans https://stackoverflow.com/a/38451745/1180351 mais plus efficace.

1
Rob Spoor

Qu'en est-il forger la méthode parseInt ?

C'est simple, il suffit de copier-coller le contenu dans un nouvel utilitaire qui renvoie Integer ou Optional<Integer> et de remplacer les jets par des retours. Il semble qu'il n'y ait pas d'exception dans le code sous-jacent, mais il vaut mieux vérifier.

En ignorant tout le processus de gestion des exceptions, vous pouvez gagner du temps sur des entrées non valides. Et la méthode existe depuis JDK 1.0, il est donc peu probable que vous deviez faire beaucoup pour la maintenir à jour.

1
Vlasec

Ils gèrent ce problème de manière récursive. Par exemple, lors de la lecture de données depuis la console:

Java.util.Scanner keyboard = new Java.util.Scanner(System.in);

public int GetMyInt(){
    int ret;
    System.out.print("Give me an Int: ");
    try{
        ret = Integer.parseInt(keyboard.NextLine());

    }
    catch(Exception e){
        System.out.println("\nThere was an error try again.\n");
        ret = GetMyInt();
    }
    return ret;
}
0
Boboman

Pour éviter une exception, vous pouvez utiliser la méthode Format.parseObject de Java. Le code ci-dessous est essentiellement une version simplifiée de la classe IntegerValidator class d'Apache Common.

public static boolean tryParse(String s, int[] result)
{
    NumberFormat format = NumberFormat.getIntegerInstance();
    ParsePosition position = new ParsePosition(0);
    Object parsedValue = format.parseObject(s, position);

    if (position.getErrorIndex() > -1)
    {
        return false;
    }

    if (position.getIndex() < s.length())
    {
        return false;
    }

    result[0] = ((Long) parsedValue).intValue();
    return true;
}

Vous pouvez utiliser AtomicInteger ou le tableau int[] en fonction de vos préférences.

Voici mon test qui l'utilise -

int[] i = new int[1];
Assert.assertTrue(IntUtils.tryParse("123", i));
Assert.assertEquals(123, i[0]);
0
Josh Unger

La réponse donnée par Jon Skeet est correcte, mais je n'aime pas rendre un objet null Integer. Je trouve cela déroutant à utiliser. Depuis Java 8, il existe une meilleure option (à mon avis), en utilisant la variable OptionalInt:

public static OptionalInt tryParse(String value) {
 try {
     return OptionalInt.of(Integer.parseInt(value));
  } catch (NumberFormatException e) {
     return OptionalInt.empty();
  }
}

Cela rend explicite le fait que vous devez gérer le cas où aucune valeur n'est disponible. Je préférerais que ce type de fonction soit ajouté à la bibliothèque Java à l'avenir, mais je ne sais pas si cela se produira un jour.

0
Marc

J'avais aussi le même problème. C'est une méthode que j'ai écrite pour demander à l'utilisateur une entrée et ne pas accepter l'entrée à moins que ce ne soit un entier. Veuillez noter que je suis un débutant, donc si le code ne fonctionne pas comme prévu, blâmez mon inexpérience!

private int numberValue(String value, boolean val) throws IOException {
    //prints the value passed by the code implementer
    System.out.println(value);
    //returns 0 is val is passed as false
    Object num = 0;
    while (val) {
        num = br.readLine();
        try {
            Integer numVal = Integer.parseInt((String) num);
            if (numVal instanceof Integer) {
                val = false;
                num = numVal;
            }
        } catch (Exception e) {
            System.out.println("Error. Please input a valid number :-");
        }
    }
    return ((Integer) num).intValue();
}
0
Abhinav Mathur

Je vous suggère d’envisager une méthode comme

 IntegerUtilities.isValidInteger(String s)

que vous mettez ensuite en œuvre à votre guise. Si vous voulez que le résultat soit reporté - peut-être parce que vous utilisez quand même Integer.parseInt () - vous pouvez utiliser l'astuce tableau.

 IntegerUtilities.isValidInteger(String s, int[] result)

où vous définissez result [0] sur la valeur entière trouvée dans le processus.

0

Ceci est une réponse à la question 8391979, "Java a-t-il un int.tryparse qui ne lève pas d'exception pour les données incorrectes? [Dupliquer]" qui est fermé et lié à cette question.

Edit 2016 08 17: Ajout des méthodes ltrimZeroes et leur appel dans tryParse (). Sans les zéros au début de numberString, cela peut donner de faux résultats (voir les commentaires dans le code). Il existe désormais une méthode publique statique String ltrimZeroes (String numberString) qui fonctionne pour les "nombres" positifs et négatifs (END Edit)

Vous trouverez ci-dessous une classe rudimentaire Wrapper (boxing) pour int avec une méthode tryParse () hautement optimisée en vitesse (similaire à C #) qui analyse la chaîne elle-même et est un peu plus rapide qu'Integer.parseInt (String s) en Java:

public class IntBoxSimple {
    // IntBoxSimple - Rudimentary class to implement a C#-like tryParse() method for int
    // A full blown IntBox class implementation can be found in my Github project
    // Copyright (c) 2016, Peter Sulzer, Fürth
    // Program is published under the GNU General Public License (GPL) Version 1 or newer

    protected int _n; // this "boxes" the int value

    // BEGIN The following statements are only executed at the
    // first instantiation of an IntBox (i. e. only once) or
    // already compiled into the code at compile time:
    public static final int MAX_INT_LEN =
            String.valueOf(Integer.MAX_VALUE).length();
    public static final int MIN_INT_LEN =
            String.valueOf(Integer.MIN_VALUE).length();
    public static final int MAX_INT_LASTDEC =
            Integer.parseInt(String.valueOf(Integer.MAX_VALUE).substring(1));
    public static final int MAX_INT_FIRSTDIGIT =
            Integer.parseInt(String.valueOf(Integer.MAX_VALUE).substring(0, 1));
    public static final int MIN_INT_LASTDEC =
            -Integer.parseInt(String.valueOf(Integer.MIN_VALUE).substring(2));
    public static final int MIN_INT_FIRSTDIGIT =
            Integer.parseInt(String.valueOf(Integer.MIN_VALUE).substring(1,2));
    // END The following statements...

    // ltrimZeroes() methods added 2016 08 16 (are required by tryParse() methods)
    public static String ltrimZeroes(String s) {
        if (s.charAt(0) == '-')
            return ltrimZeroesNegative(s);
        else
            return ltrimZeroesPositive(s);
    }
    protected static String ltrimZeroesNegative(String s) {
        int i=1;
        for ( ; s.charAt(i) == '0'; i++);
        return ("-"+s.substring(i));
    }
    protected static String ltrimZeroesPositive(String s) {
        int i=0;
        for ( ; s.charAt(i) == '0'; i++);
        return (s.substring(i));
    }

    public static boolean tryParse(String s,IntBoxSimple intBox) {
        if (intBox == null)
            // intBoxSimple=new IntBoxSimple(); // This doesn't work, as
            // intBoxSimple itself is passed by value and cannot changed
            // for the caller. I. e. "out"-arguments of C# cannot be simulated in Java.
            return false; // so we simply return false
        s=s.trim(); // leading and trailing whitespace is allowed for String s
        int len=s.length();
        int rslt=0, d, dfirst=0, i, j;
        char c=s.charAt(0);
        if (c == '-') {
            if (len > MIN_INT_LEN) { // corrected (added) 2016 08 17
                s = ltrimZeroesNegative(s);
                len = s.length();
            }
            if (len >= MIN_INT_LEN) {
                c = s.charAt(1);
                if (!Character.isDigit(c))
                    return false;
                dfirst = c-'0';
                if (len > MIN_INT_LEN || dfirst > MIN_INT_FIRSTDIGIT)
                    return false;
            }
            for (i = len - 1, j = 1; i >= 2; --i, j *= 10) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt -= (c-'0')*j;
            }
            if (len < MIN_INT_LEN) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt -= (c-'0')*j;
            } else {
                if (dfirst >= MIN_INT_FIRSTDIGIT && rslt < MIN_INT_LASTDEC)
                    return false;
                rslt -= dfirst * j;
            }
        } else {
            if (len > MAX_INT_LEN) { // corrected (added) 2016 08 16
                s = ltrimZeroesPositive(s);
                len=s.length();
            }
            if (len >= MAX_INT_LEN) {
                c = s.charAt(0);
                if (!Character.isDigit(c))
                    return false;
                dfirst = c-'0';
                if (len > MAX_INT_LEN || dfirst > MAX_INT_FIRSTDIGIT)
                    return false;
            }
            for (i = len - 1, j = 1; i >= 1; --i, j *= 10) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt += (c-'0')*j;
            }
            if (len < MAX_INT_LEN) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt += (c-'0')*j;
            }
            if (dfirst >= MAX_INT_FIRSTDIGIT && rslt > MAX_INT_LASTDEC)
                return false;
            rslt += dfirst*j;
        }
        intBox._n=rslt;
        return true;
    }

    // Get the value stored in an IntBoxSimple:
    public int get_n() {
        return _n;
    }
    public int v() { // alternative shorter version, v for "value"
        return _n;
    }
    // Make objects of IntBoxSimple (needed as constructors are not public):
    public static IntBoxSimple makeIntBoxSimple() {
        return new IntBoxSimple();
    }
    public static IntBoxSimple makeIntBoxSimple(int integerNumber) {
        return new IntBoxSimple(integerNumber);
    }

    // constructors are not public(!=:
    protected IntBoxSimple() {} {
        _n=0; // default value an IntBoxSimple holds
    }
    protected IntBoxSimple(int integerNumber) {
        _n=integerNumber;
    }
}

Programme de test/exemple pour la classe IntBoxSimple:

import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStreamReader;
public class IntBoxSimpleTest {
    public static void main (String args[]) {
        IntBoxSimple ibs = IntBoxSimple.makeIntBoxSimple();
        String in = null;
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        do {
            System.out.printf(
                    "Enter an integer number in the range %d to %d:%n",
                        Integer.MIN_VALUE, Integer.MAX_VALUE);
            try { in = br.readLine(); } catch (IOException ex) {}
        } while(! IntBoxSimple.tryParse(in, ibs));
        System.out.printf("The number you have entered was: %d%n", ibs.v());
    }
}
0
Peter Sulzer

Essayez avec l'argument expression régulière et paramètres par défaut

public static int parseIntWithDefault(String str, int defaultInt) {
    return str.matches("-?\\d+") ? Integer.parseInt(str) : defaultInt;
}


int testId = parseIntWithDefault("1001", 0);
System.out.print(testId); // 1001

int testId = parseIntWithDefault("test1001", 0);
System.out.print(testId); // 1001

int testId = parseIntWithDefault("-1001", 0);
System.out.print(testId); // -1001

int testId = parseIntWithDefault("test", 0);
System.out.print(testId); // 0

si vous utilisez Apache.commons.lang3, utilisez NumberUtils :

int testId = NumberUtils.toInt("test", 0);
System.out.print(testId); // 0
0
Krunal

Vous pourriez rouler le vôtre, mais il est tout aussi facile d'utiliser la fonction StringUtils.isNumeric()méthode de commons lang _ _. Il utilise Character.isDigit () pour parcourir chaque caractère de la chaîne.

0
James Bassett

J'aimerais ajouter une autre proposition qui fonctionne si l'on demande spécifiquement des entiers: utilisez simplement long et utilisez Long.MIN_VALUE pour les cas d'erreur. Cette approche est similaire à celle utilisée pour les caractères dans Reader dans laquelle Reader.read () renvoie un entier compris dans la plage d'un caractère ou -1 si le lecteur est vide.

Pour Float et Double, NaN peut être utilisé de la même manière.

public static long parseInteger(String s) {
    try {
        return Integer.parseInt(s);
    } catch (NumberFormatException e) {
        return Long.MIN_VALUE;
    }
}


// ...
long l = parseInteger("ABC");
if (l == Long.MIN_VALUE) {
    // ... error
} else {
    int i = (int) l;
}
0
Searles

Vous ne devriez pas utiliser Exceptions pour valider vos valeurs.

Pour un caractère unique, il existe une solution simple:

Character.isDigit()

Pour des valeurs plus longues, il vaut mieux utiliser des utils. NumberUtils fourni par Apache fonctionnerait parfaitement ici:

NumberUtils.isNumber()

Veuillez vérifier https://commons.Apache.org/proper/commons-lang/javadocs/api-2.6/org/Apache/commons/lang/math/NumberUtils.html

0
Bogdan Aksonenko

Ceci est un peu similaire à la solution de Nikolay:

 private static class Box<T> {
  T me;
  public Box() {}
  public T get() { return me; }
  public void set(T fromParse) { me = fromParse; }
 }

 private interface Parser<T> {
  public void setExclusion(String regex);
  public boolean isExcluded(String s);
  public T parse(String s);
 }

 public static <T> boolean parser(Box<T> ref, Parser<T> p, String toParse) {
  if (!p.isExcluded(toParse)) {
   ref.set(p.parse(toParse));
   return true;
  } else return false;
 }

 public static void main(String args[]) {
  Box<Integer> a = new Box<Integer>();
  Parser<Integer> intParser = new Parser<Integer>() {
   String myExclusion;
   public void setExclusion(String regex) {
    myExclusion = regex;
   }
   public boolean isExcluded(String s) {
    return s.matches(myExclusion);
   }
   public Integer parse(String s) {
    return new Integer(s);
   }
  };
  intParser.setExclusion("\\D+");
  if (parser(a,intParser,"123")) System.out.println(a.get());
  if (!parser(a,intParser,"abc")) System.out.println("didn't parse "+a.get());
 }

La méthode principale montre le code. Une autre façon d'implémenter l'interface Parser serait évidemment de définir "\ D +" à partir de la construction et de laisser les méthodes inutilisées.

0
Carl