Différence entre public, protected, package-private et private en Java
Java possède quatre niveaux de visibilité pour les classes, méthodes et champs. Bien les comprendre est fondamental pour écrire du code encapsulé, testable et maintenable.
Les quatre modificateurs
| Modificateur | Même classe | Même package | Sous-classe | Partout |
|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ✅ | ❌ |
| package-private (pas de mot-clé) | ✅ | ✅ | ❌ | ❌ |
private | ✅ | ❌ | ❌ | ❌ |
public — visible partout
Accessible depuis n'importe quelle classe, n'importe quel package. C'est la visibilité des APIs publiques : System.out, List, String.valueOf.
package com.example.api;
public class Calculator {
public int add(int a, int b) { return a + b; }
}
Règle pratique : seul ce qui fait partie du contrat public de votre bibliothèque doit être public. Tout le reste devrait être plus restreint.
protected — pour les héritiers
Accessible depuis la classe, toute classe du même package, et toute sous-classe — même dans un autre package.
package com.example.shapes;
public class Shape {
protected double area; // accessible aux sous-classes
protected double computeArea() { return 0; } // override possible
}
package com.other.shapes;
import com.example.shapes.Shape;
public class Circle extends Shape {
public Circle(double r) {
this.area = Math.PI * r * r; // ✅ accès à area hérité
}
@Override
protected double computeArea() { return area; } // ✅ override
}
Usage typique : méthodes d'un framework que les utilisateurs peuvent redéfinir mais pas appeler directement depuis l'extérieur.
package-private — le défaut silencieux
Si vous n'écrivez aucun modificateur, Java applique la visibilité package-private : la classe ou le membre est accessible uniquement depuis le même package.
package com.example.service;
class InternalHelper { // pas de modificateur = package-private
static void validate() { ... }
}
public class OrderService {
public void process() {
InternalHelper.validate(); // ✅ même package
}
}
Parfait pour cacher des détails d'implémentation sans pour autant enterrer tout le code dans une seule classe. La visibilité package-private est souvent sous-utilisée — privilégiez-la systématiquement aux classes public qui n'ont pas vraiment besoin de l'être.
private — visible dans la classe uniquement
Le plus restrictif : seuls la classe elle-même et ses classes imbriquées y ont accès.
public class Wallet {
private double solde;
private boolean montantValide(double m) { return m > 0; }
public void crediter(double montant) {
if (montantValide(montant)) solde += montant;
}
}
Règle : tout champ qui n'est pas destiné à être modifié de l'extérieur doit être private. C'est la base de l'encapsulation.
Particularités pour les classes
Au niveau top-level, seuls public et package-private sont permis — une classe top-level ne peut être ni private ni protected.
// ✅ Légal
public class A { }
class B { } // package-private
// ❌ Erreur de compilation
private class C { }
Les classes internes, elles, peuvent porter n'importe quel modificateur :
public class Outer {
private static class InnerHelper { } // classe interne privée
protected class Tool { }
static class Nested { }
}
Modules Java (depuis Java 9)
Les modules introduisent une cinquième couche au-dessus des packages. Même un type public n'est pas accessible depuis un autre module si le package qui le contient n'est pas exported dans module-info.java :
module com.example.shapes {
exports com.example.shapes.api; // accessible
// com.example.shapes.internal reste interne
}
Exemple d'architecture bien encapsulée
package com.example.order;
public class OrderService {
private final OrderRepository repo;
private final Validator validator; // classe interne au package
public OrderService(OrderRepository repo) {
this.repo = repo;
this.validator = new Validator();
}
public Order create(Cart cart) {
validator.validate(cart); // détail d'implémentation
Order order = new Order(cart);
repo.save(order);
return order;
}
}
// Package-private : pas exposée hors du package
class Validator {
void validate(Cart cart) { /* ... */ }
}
Le client externe voit OrderService et create() uniquement. Tout le reste (Validator, détails internes) est caché.
Bonnes pratiques
- Principe du moindre privilège : choisir la visibilité la plus restrictive qui fonctionne. Si
privatesuffit, pas besoin depublic. - Pas de
publicsur les champs non finaux — sauf constantes (public static final). - Préférer package-private aux classes publiques pour tout ce qui n'est pas API publique.
- Éviter
protectedsur les champs — il casse l'encapsulation entre sous-classes sans vraiment la restreindre. - Pour les tests, garder la classe testée
publicsi nécessaire, mais privilégier des méthodes package-private pour accéder à l'état interne depuis un test dans le même package.
Un code Java bien encapsulé commence par une visibilité choisie avec soin, pas par le réflexe d'écrire public partout.