Techniques d'optimisation du code Java
- 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);
}
}
}
- 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()));