Posts tagged: optimisation

Revue de code II

Suite de la revue de code : BigDecimal, nommage et concaténation de String.

Read more »

Revues de code I

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.

Comparaison avec == sur des java.lang.Integer

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.

Utiliser plutôt valueOf()  plutôt que new

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)
Returns a Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor 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) …)

Éviter d’utiliser new String

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.

Se méfier de la construction des BigDecimal.

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)
Translates a 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:

  1. The results of this constructor can be somewhat unpredictable. One might assume that writing 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.
  2. The 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.
  3. When a 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.