Revue de code II
Suite de la revue de code : BigDecimal, nommage et concaténation de String.
Suite de la revue de code : BigDecimal, nommage et concaténation de String.
Au cours de mes revues de code, je tombe sur des problèmes plus ou moins réguliers. Outre les problèmes de designs, certains aspects techniques sont récurrents.
Le problème, c’est que sur des integers entre -128 et 127, cela peut marcher. Dans la suite de l’exemple, integer1 et integer2 seront de type java.lang.Integer, int1 sera de type primitif int.
Integer integer1 = 127; Integer integer2 = 127; System.out.println(integer1 == integer2); // true integer1 = 128; integer2 = 128; System.out.println(integer1 == integer2); // false
Par contre, on a bien pour toutes les valeurs, y compris celles entre -128 et 127 :
integer1 = new Integer(127); integer2 = new Integer(127); System.out.println(integer1 == integer2); // false - normal on a instancié 2 objects différents integer1 = new Integer(129); int1 = 129; System.out.println(integer1 == integer2); // true
Si on regarde la doc de Sun, pour tous les objets entre -128 et 127, l’autoboxing garantit que l’objet renvoyé Integer sera identique pour un même entier primitif. Ce qui ne sera pas le cas au delà de l’intervalle. De plus, rien ne garantit qu’une implémentation étende l’intervalle de cache, raison pour laquelle il est risqué de se baser dessus.
Byte, Short, Long ont également le même type de cache. Character a également un cache mais de 0 à 127 (un nombre négatif n’aurait pas eu de sens).
d. Autoboxing is guaranteed to return the same object for integral
values in the range [-128, 127], but an implementation may, at its
discretion, cache values outside of that range. It would be bad style
http://java.sun.com/developer/JDCTechTips/2005/tt0405.html#1
Ainsi, utiliser == en se fiant à l’autoboxing et au cache est une mauvaise pratique, même pour comparer des constantes de faible valeur.
Il est préferable d’utiliser un valueOf plutôt qu’une nouvelle instanciation à l’aide du constructeur. Valable pour la certains objets immutables.
Exemple avec des types java.lang.Integer, si on regarde la javadoc pour la méthode Integer.valueOf(int i) :
public static Integer valueOf(int i)
Integer(int)
, as this method is likely to yield significantly better space and time performance by caching frequently requested values. Idem pour les Byte, Long, Character, BigInteger … Par contre, préférer quand c’est possible de construire les valeurs accessibles directement via une méthode statique (BigInteger.ONE plutôt que BigInteger.valueOf(1), Boolean.FALSE plutôt que Boolean.valueOf(false) …)
String test1 = "abc"; String test2 = "abc"; String test3 = new String("abc"); String test4 = new String("abc"); System.out.println(test1 == test2); // true System.out.println(test1 == test3); //false System.out.println(test3 == test4); //false
Utiliser new String implique de créer un nouvel object en mémoire. Les String étant immutables, avoir 2 objets différents ayant la même valeur ne fait que consommer de la mémoire. Attention à bien noter le cas 2, « abc » est différent de new String(« abc »). Cet exemple permet également de montrer l’importance d’utiliser la méthode equals pour comparer les valeurs des objets java.lang.String .
Comme précisé par Jérôme et Nicolas Le Coz en commentaire, l’instanciation d’un nouvel objet String est conseillée lors de l’utilisation de substring sur des très grandes chaînes de caractères. En effet, la valeur stockée avec un substring n’est pas uniquement la substring, mais la chaîne entière avec un offset (premier caractère à utiliser) et un compteur (nombre de caractères à utiliser).
C’est clair si l’on regarde la classe java.lang.String
public final class String implements java.io.Serializable, Comparable, CharSequence { /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; [...] public String substring(int beginIndex, int endIndex) { [...] return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); }
Plus d’infos ici http://blog.xebia.fr/2009/08/03/revue-de-presse-xebia-120/#AttentionvotremmoireavecString et dans les autres liens en commentaires.
On utilise ici 3 manières de faire pour créer un BigDecimal que l’on souhaite égal à 4.12. Néanmoins, les résultats ne sont pas conformes à ce qu’on pourrait en attendre.
Double myDouble = 4.12D; BigDecimal bd1 = new BigDecimal(myDouble); BigDecimal bd2 = new BigDecimal(String.valueOf(myDouble)); BigDecimal bd3 = BigDecimal.valueOf(myDouble); System.out.println(bd1.equals(bd2)); // false ! System.out.println(bd1.equals(bd3)); // false ! System.out.println(bd2.equals(bd3)); // true
Il n’y a donc pas égalité de valeur entre l’object construit à partir du double et les 2 autres objets. En effet, si on affiche les différentes valeurs des 3 BigDecimal, le problème est facilement visible :
RESULT 1: 4.12000000000000010658141036401502788066864013671875
RESULT 2: 4.12
RESULT 3: 4.12
Il vaut mieux donc éviter d’utiliser le constructeur BigDecimal(double double) et de lui préférer les deux autres méthodes.
public BigDecimal(double val)
double
into a BigDecimal
which is the exact decimal representation of the double
‘s binary floating-point value. The scale of the returned BigDecimal
is the smallest value such that (10scale × val) is an integer.Notes:
new BigDecimal(0.1)
in Java creates a BigDecimal
which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double
(or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.String
constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1")
creates a BigDecimal
which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.double
must be used as a source for a BigDecimal
, note that this constructor provides an exact conversion; it does not give the same result as converting the double
to a String
using the Double.toString(double)
method and then using the BigDecimal(String)
constructor. To get that result, use the static
valueOf(double)
method.