Getters, setters et constructeurs en Java

Les getters, setters et constructeurs sont les trois piliers de l'encapsulation en programmation orientée objet Java. Ils contrôlent comment les données d'un objet sont initialisées, lues et modifiées.

Pourquoi encapsuler ?

Déclarer les champs private empêche l'accès direct depuis l'extérieur. Les accesseurs publics deviennent le seul point d'entrée — ce qui permet :

  • de valider les données avant de les affecter ;
  • de changer l'implémentation interne sans casser les appelants ;
  • de logger les accès, calculer des valeurs dérivées, etc.

Le constructeur

Il sert à créer un objet en initialisant ses champs dans un état valide.

public class Utilisateur {
    private String nom;
    private int age;

    // Constructeur explicite
    public Utilisateur(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }
}

Utilisateur alice = new Utilisateur("Alice", 30);

Règles importantes

  • Son nom correspond exactement à celui de la classe.
  • Il n'a pas de type de retour, pas même void.
  • Si vous n'en définissez aucun, Java fournit un constructeur par défaut sans argument.
  • Dès que vous définissez un constructeur avec arguments, ce constructeur par défaut disparaît.

Surcharger plusieurs constructeurs

Une classe peut proposer plusieurs constructeurs pour offrir de la flexibilité :

public class Utilisateur {
    private String nom;
    private int age;

    public Utilisateur() {
        this("Anonyme", 0); // appel d'un autre constructeur
    }

    public Utilisateur(String nom) {
        this(nom, 0);
    }

    public Utilisateur(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }
}

this(...) doit être la première instruction du constructeur.

Le getter

Par convention : getNomDuChamp(), ou isNomDuChamp() pour un booléen.

public String getNom() {
    return nom;
}

public boolean isActif() {
    return actif;
}

Cette convention est cruciale : de nombreux frameworks (Spring, Jackson, Hibernate) détectent les propriétés d'un objet uniquement à partir de ces préfixes.

Le setter

Par convention : setNomDuChamp(valeur). Il est idéal pour valider :

public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("Age invalide : " + age);
    }
    this.age = age;
}

Exemple complet

public class Utilisateur {
    private String nom;
    private int age;
    private boolean actif;

    public Utilisateur(String nom, int age) {
        this.nom = nom;
        setAge(age);    // passe par le setter pour la validation
        this.actif = true;
    }

    public String getNom() { return nom; }
    public void setNom(String nom) {
        if (nom == null || nom.isBlank()) {
            throw new IllegalArgumentException("Nom requis");
        }
        this.nom = nom;
    }

    public int getAge() { return age; }
    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Age invalide");
        }
        this.age = age;
    }

    public boolean isActif() { return actif; }
    public void setActif(boolean actif) { this.actif = actif; }
}

Générer automatiquement dans l'IDE

  • IntelliJ IDEA : Alt + InsertGetter and Setter.
  • Eclipse : clic droit → Source → Generate Getters and Setters….
  • VS Code (extension Java) : Ctrl + . sur le champ → Generate Accessors.

Simplifier avec Lombok

La bibliothèque Lombok génère les accesseurs à la compilation :

import lombok.Getter;
import lombok.Setter;
import lombok.AllArgsConstructor;

@Getter @Setter @AllArgsConstructor
public class Utilisateur {
    private String nom;
    private int age;
}

La classe a tous les accesseurs et un constructeur complet, sans verbosité. Attention : la validation doit être ajoutée manuellement via @Setter(value = AccessLevel.NONE) + setter explicite.

Et les records ?

Depuis Java 16, un record est la manière la plus concise de déclarer une classe de données immuable. Les accesseurs sont générés automatiquement (mais sans préfixe get) et il n'y a pas de setter :

public record Utilisateur(String nom, int age) { }

Utilisateur u = new Utilisateur("Alice", 30);
u.nom(); // "Alice"
u.age(); // 30

Pour ajouter de la validation, un constructeur compact fait l'affaire :

public record Utilisateur(String nom, int age) {
    public Utilisateur {
        if (age < 0) throw new IllegalArgumentException("Age invalide");
    }
}

Bonnes pratiques

  • Gardez les champs private.
  • N'ajoutez un setter que si la mutation est vraiment nécessaire — la préférence va aux objets immuables.
  • Validez dans le setter, pas chez l'appelant.
  • Pour les classes purement de données : record en priorité, sinon Lombok.
  • Écrivez toString(), equals() et hashCode() en même temps (ou laissez-les être générés).

Maîtriser ces trois mécanismes permet d'écrire du code Java propre, testable et conforme aux attentes des frameworks de l'écosystème.