nov.
2011
Aujourd'hui, je vous propose une petite révision sur la conversion entre les différentes représentations des nombres.
En Java, il est possible de représenter un nombre sous 3 formes :
- en utilisant le type primitif adéquat (
byte
,short
,int
,long
,float
,double
) - en utilisant la classe wrapper correspondante (
Byte
,Short
,Integer
,Long
,Float
,Double
) - sous forme de chaîne de caractères, provenant le plus souvent d'une saisie utilisateur
Il existe des méthodes optimisées pour opérer des conversion entre ces trois formats. Il est important de les connaître, pour des raisons de lisibilité et de performance.
Dans cet article, je prendrai le type entier (int
/ Integer
) comme exemple, mais les techniques présentées s'appliquent de manière semblable aux autres types numériques.
Egalement, les méthodes somePrimitive()
, someWrapper()
et someText()
sont définies comme suit :
private int somePrimitive() {return 1;} private Integer someWrapper() {return Integer.valueOf(1);} private String someText() {return "1";}
Primitive <-> String
Primitive -> String
Commençons par la conversion qui pose le plus souvent problème : la conversion d'un nombre en texte.
Les développeurs débutants utilisent souvent la technique de la concaténation entre une chaîne vide et le nombre. Cette approche, si elle a le mérite de la simplicité, est malheureusement la moins performante - je vous en reparlerai dans un prochain billet.
En attendant, faites-moi confiance : utilisez la méthode Integer.toString()
!
public void primitiveToString() { // Naive approach : slow String text1 = "" + somePrimitive(); // Optimized approach String text2 = Integer.toString(somePrimitive()); }
String -> Primitive
Pour convertir une chaîne en type primitif, utilisez Integer.parseInt()
. Si la chaîne à convertir ne représente pas un nombre valide, un NumberFormatException
peut être levé.
public void stringToPrimitive() { // Straightforward parsing, but watch out for NumberFormatException ! int primitive1 = Integer.parseInt(someText()); // Parsing with exception management int primitive2; try { primitive2 = Integer.parseInt(someText()); } catch (NumberFormatException e) { // Apply some default value. Do not let the "catch" block empty ! primitive2 = -1; } }
String <-> Wrapper
String-> Wrapper
Attention, deuxième piège à performance !
Pour convertir un texte (ou un type primitif) en wrapper, il est recommandé d'utiliser la méthode factory Integer.valueOf()
plutôt que d'instancier manuellement le wrapper. En effet, pour des raisons de performances, certains des wrappers les plus courants (entre -128 et +127) sont mis en cache, économisant ainsi de la mémoire et du travail au garbage collector.
(Je vous renvoie au Quiz 44 pour une explication détaillée sur les caches de wrappers.)
public void stringToWrapper() { // Naive approach : new wrapper instance each time Integer wrapper1 = new Integer(someText()); // Optimized approach : using the wrapper cache Integer wrapper2 = Integer.valueOf(someText()); // Optimized approach, with exception management Integer wrapper3; try { wrapper3 = Integer.valueOf(someText()); } catch (NumberFormatException e) { wrapper3 = Integer.valueOf(-1); } }
Wrapper -> String
La méthode toSring()
habituelle permet de transformer un wrapper en String
. Facile.
public void wrapperToString() { String text = someWrapper().toString(); }
Wrapper <-> Primitive
Wrapper -> Primitive
Extraire un type primitif à partir d'un wrapper est également très simple. Détail intéressant, il est possible d'obtenir un type primitif de type différent de celui du wrapper : par exemple, Integer
propose naturellement la méthode intValue()
, mais également floatValue()
, longValue()
, etc., définies dans la superclasse Number
.
Les wrappers étant des objets, il peuvent être nuls ; attention donc au NullPointerException
lors des conversions !
Cet avertissement vous paraît sûrement superflu, mais l'auto-unboxing réserve parfois des surprises...
public void wrapperToPrimitive() { // Conversion to the associated primitive type int primitive1 = someWrapper().intValue(); // Conversion to another primitive type float primitive2 = someWrapper().floatValue(); }
Primitive -> Wrapper
Comme vu plus haut, il est préférable d'utiliser Integer.valueOf()
afin de tirer parti du cache des wrappers.
public void primitiveToWrapper() { Integer wrapper = Integer.valueOf(somePrimitive()); }
Autoboxing
L'auto-boxing, apparu avec Java 5, essaie de simplifier la vie du développeur en rendant transparente la conversion entre un type primitif et son wrapper.
L'intention est louable, et la réalisation habituellement satisfaisante car elle utilise automatiquement le cache des wrappers :
public void autoBoxing() { // Auto-boxing : primitive -> wrapper using cache Integer wrapper = somePrimitive(); // Auto-unboxing : wrapper -> primitive. Watch out for NullPointerException ! int primitive = someWrapper(); }
Pourtant, il faut s'en méfier pour deux raisons :
- Gare au
NullPointerExption
lorsqu'un wrapper null est auto-unboxé ! Le cas le plus courant est un wrapper null passé en paramètre d'une méthode l'utilisant directement au sein d'une expression mathématique. - Gare aux performances lors de la génération de collections de nombres ! Les collections de types primitifs n'existant pas en Java, les valeurs sont automatiquement encapsulées (auto-boxing) dans des wrappers, qui, en tant qu'objets, occupent beaucoup de place en mémoire. Si le nombre d'éléments peut être connu à l'avance, il est donc préférable d'utiliser des tableaux de types primitifs.
public void autoUnboxingProblems() { // Warning : NullPointerException ! Integer wrapper = null; int primitive = wrapper.intValue(); // Warning : auto-boxing creates 1'000'000 wrapper instances ! List<Number> numbers = new ArrayList<Number>(); for (int i = 0; i < 1000000; i++) { numbers.add(i); } }
Conclusion
La conversion entre les trois formes possibles d'un nombre est facile en Java, mais il est préférable de bien connaître les performances et avantages/inconvénients de chaque méthode.
Je vous recommande pour cela d'étudier la javadoc des classes wrappers ; elle n'est pas très longue, mais vous y (re-)découvrirez sûrement quelques fonctions utiles pour gérer vos nombres de manière optimisée.
Commentaires
Merci, bel article très clair, comme d'hab.
Les classes BigDecimal et BigInteger n'auraient-elles pas leur place dans cet article?
Vous n'avez pas évoqué les classes java.math.BigInteger et java.math.BigDecimal, qui sont pourtant aussi un moyen de représenter des nombres en Java. Mais c'est probablement pour des raisons didactiques...