Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Au coeur du JDK : conversion des nombres

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

1. Le lundi 7 novembre 2011, 14:50 par yannick

Merci, bel article très clair, comme d'hab.
Les classes BigDecimal et BigInteger n'auraient-elles pas leur place dans cet article?

2. Le mardi 8 novembre 2011, 08:51 par laurent

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...

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.