Optimisation des performances en Java

Techniques d'optimisation du code Java

  1. Constantes et Variables

1.1. Assignation directe des valeurs littérales

L'assignation directe d'une valeur littérale crée une référence vers une instance partagée, évitant ainsi des allocations inutiles.

Contre-exemple :

// Création superflue d'objets
Long identifiant = new Long(100L);
Texte chaine = new Texte("bonjour");

Bon exemple :

// Utilisation de littéraux
Long identifiant = 100L;
Texte chaine = "bonjour";

1.2. Préférer les constantes statiques pour les valeurs immuables

Les constantes statiques partagent une seule instance pour toute l'application, réduisant l'empreinte mémoire.

Contre-exemple :

public class ServiceReseau {
    private final long delaiExpiration = 5000L; // Réplication par instance
}

Bon exemple :

public class ServiceReseau {
    private static final long DELAI_EXPIRATION = 5000L; // Instance unique
}

1.3. Éviter l'auto-boxing avec des types primitifs

L'utilisation de types primitifs au lieu de leurs enveloppes (wrappres) élimine les conversions implicites coûteuses en ressources.

Contre-exemple :

Integer accumulation = 0;
int[] donnees = ...;
for (int element : donnees) {
    accumulation += element; // Boxing implicite
}

Bon exemple :

int accumulation = 0;
int[] donnees = ...;
for (int element : donnees) {
    accumulation += element; // Opération primitive directe
}

1.4. Ne pas initialiser les variables subissant une réaffectation immédiate

Éviter une affectation initiale inutile lorsque la valeur est remplacée peu après.

Contre-exemple :

Liste<Utilisateur> utilisateurs = new ArrayList<>();
if (modeComplet) {
    utilisateurs = depot.rechercherTous();
} else {
    utilisateurs = depot.rechercherActifs();
}

Bon exemple :

Liste<Utilisateur> utilisateurs;
if (modeComplet) {
    utilisateurs = depot.rechercherTous();
} else {
    utilisateurs = depot.rechercherActifs();
}

1.5. Favoriser les variables locales pour les calculs intensifs

Les variables locales sont allouées sur la pile (stack), offrant un accès plus rapide que les champs d'objets stockés dans le tas (heap).

Contre-exemple :

public class Calculateur {
    private double cumul = 0.0;
    public void totaliser(double[] valeurs) {
        for (double val : valeurs) {
            cumul += val; // Accès répété au champ d'instance
        }
    }
}

Bon exemple :

public class Calculateur {
    private double cumul = 0.0;
    public void totaliser(double[] valeurs) {
        double sommeIntermediaire = 0.0;
        for (double val : valeurs) {
            sommeIntermediaire += val; // Calcul local rapide
        }
        cumul += sommeIntermediaire; // Mise à jour unique
    }
}

1.6. Définir les variables dans la portée la plus restreinte possible

Les compilateurs modernes optimisent la portée des variables, mais une déclaration dans la boucle améliore la lisibilité et la gestion de la mémoire.

Contre-exemple :

VueUtilisateur vue;
Liste<ModeleUtilisateur> modeles = ...;
Liste<VueUtilisateur> vues = new ArrayList<>(modeles.taille());
for (ModeleUtilisateur modele : modeles) {
    vue = new VueUtilisateur();
    vue.definirId(modele.obtenirId());
    // ...
    vues.ajouter(vue);
}

Bon exemple :

Liste<ModeleUtilisateur> modeles = ...;
Liste<VueUtilisateur> vues = new ArrayList<>(modeles.taille());
for (ModeleUtilisateur modele : modeles) {
    VueUtilisateur vue = new VueUtilisateur();
    vue.definirId(modele.obtenirId());
    // ...
    vues.ajouter(vue);
}

1.7. Utiliser des collections non sécurisées pour les constantes immuables

Les constantes immuables, même partagées entre threads, peuvent utiliser des structures non synchronisées pour plus d'efficacité.

Contre-exemple :

public static final Map<String, Classe> CORRESPONDANCE_TYPES;
static {
    Map<String, Classe> correspondance = new ConcurrentHashMap<>(16);
    correspondance.placer("CHAINE", String.class);
    // ...
    CORRESPONDANCE_TYPES = Collections.carteNonModifiable(correspondance);
}

Bon exemple :

public static final Map<String, Classe> CORRESPONDANCE_TYPES;
static {
    Map<String, Classe> correspondance = new HashMap<>(16);
    correspondance.placer("CHAINE", String.class);
    // ...
    CORRESPONDANCE_TYPES = Collections.carteNonModifiable(correspondance);
}

1.8. Appliquer la même logique pour les champs d'objets immuables

Les champs finals peuvent aussi bénéficier de collections non thread-safe lors de l'initialisation unique.

Contre-exemple :

@Service
public class FabriqueStrategie implements InitialisationProprietes {
    @Inject
    private Liste<Strategie> strategieListe;
    private Map<String, Strategie> strategieCarte;
    @Override
    public void apresProprietesDefinies() {
        if (!strategieListe.estVide()) {
            int taille = (int) Math.ceil(strategieListe.taille() * 4.0 / 3);
            Map<String, Strategie> temp = new ConcurrentHashMap<>(taille);
            for (Strategie s : strategieListe) {
                temp.placer(s.type(), s);
            }
            strategieCarte = Collections.carteNonModifiable(temp);
        }
    }
}

Bon exemple :

@Service
public class FabriqueStrategie implements InitialisationProprietes {
    @Inject
    private Liste<Strategie> strategieListe;
    private Map<String, Strategie> strategieCarte;
    @Override
    public void apresProprietesDefinies() {
        if (!strategieListe.estVide()) {
            int taille = (int) Math.ceil(strategieListe.taille() * 4.0 / 3);
            Map<String, Strategie> temp = new HashMap<>(taille);
            for (Strategie s : strategieListe) {
                temp.placer(s.type(), s);
            }
            strategieCarte = Collections.carteNonModifiable(temp);
        }
    }
}
  1. Objets et Classes

2.1. Proscrire la sérialisation JSON pour la conversion d'objets

La conversion via JSON introduit une surcharge due à l'analyse et à la génération de chaînes, souvent évitable.

Contre-exemple :

Liste<ModeleUtilisateur> modeles = ...;
Liste<VueUtilisateur> vues = JSON.analyserTableau(JSON.enChaine(modeles), VueUtilisateur.class);

Bon exemple :

Liste<ModeleUtilisateur> modeles = ...;
Liste<VueUtilisateur> vues = new ArrayList<>(modeles.taille());
for (ModeleUtilisateur m : modeles) {
    VueUtilisateur v = new VueUtilisateur();
    v.definirId(m.obtenirId());
    // ...
    vues.ajouter(v);
}

2.2. Minimiser l'utilisation de la réflexion

La réflexion permet un code plus concis mais détériore les performances à cause des vérifications dynamiques.

Contre-exemple :

Liste<ModeleUtilisateur> modeles = ...;
Liste<VueUtilisateur> vues = new ArrayList<>(modeles.taille());
for (ModeleUtilisateur m : modeles) {
    VueUtilisateur v = new VueUtilisateur();
    OutilsProprietes.copierProprietes(m, v); // Réflexion
    vues.ajouter(v);
}

Bon exemple :

Liste<ModeleUtilisateur> modeles = ...;
Liste<VueUtilisateur> vues = new ArrayList<>(modeles.taille());
for (ModeleUtilisateur m : modeles) {
    VueUtilisateur v = new VueUtilisateur();
    v.definirId(m.obtenirId());
    // ...
    vues.ajouter(v);
}

2.3. Préférer les expressions lambda aux classes anonymes

Les expressinos lambda utilisent l'instruction invokeDynamic, offrant une melileure optimisation par la JVM.

Contre-exemple :

Liste<Utilisateur> utilisateurs = ...;
Collections.trier(utilisateurs, new Comparateur<Utilisateur>() {
    @Override
    public int comparer(Utilisateur u1, Utilisateur u2) {
        return u1.obtenirId().comparerA(u2.obtenirId());
    }
});

Bon exemple :

Liste<Utilisateur> utilisateurs = ...;
Collections.trier(utilisateurs, (u1, u2) -> u1.obtenirId().comparerA(u2.obtenirId()));

Étiquettes: Java Optimisation des performances Gestion de la mémoire bonnes pratiques de codage collections

Publié le 10 juillet à 20h52