Ce qu'est une classe abstraite
Une interface déclare un comportement sans état. Une classe ordinaire est entièrement implémentée et peut être instanciée. Une classe abstraite se situe entre les deux : elle peut porter des champs, des constructeurs et des méthodes terminées comme une classe normale, mais elle peut aussi laisser certaines méthodes non implémentées et interdit d'être instanciée directement. Vous la marquez avec le mot-clé abstract.
L'idée est de rassembler en un seul endroit tout ce que les sous-classes ont en commun, tout en forçant chaque sous-classe à compléter les parties qui diffèrent réellement.
Animal définit getName() une seule fois pour toutes les sous-classes et déclare sound() comme abstract : une méthode dotée d'une signature mais sans corps, que Dog doit fournir.
Vous ne pouvez pas instancier une classe abstraite
Comme une classe abstraite peut comporter des méthodes inachevées, la créer directement vous laisserait avec un objet incomplet. Le compilateur le refuse :
Animal a = new Animal("???"); // error: Animal is abstract; cannot be instantiated
Vous instanciez toujours une sous-classe concrète, c'est-à-dire une sous-classe qui a implémenté chaque méthode abstraite. Cette instance de sous-classe peut ensuite être conservée dans une variable du type abstrait, et c'est exactement ainsi que l'on utilise l'abstraction.
Les méthodes abstraites forcent les sous-classes à décider
Une méthode abstract est une promesse que la sous-classe doit tenir. Si une sous-classe oublie d'en implémenter une, la sous-classe elle-même devient abstraite et le compilateur vous le signale. C'est le levier principal de la classe abstraite : elle garantit qu'un certain comportement existe sans dicter ce qu'il fait.
describe() n'est écrit qu'une seule fois dans Shape, et pourtant il appelle le area() propre à chaque sous-classe. La classe abstraite fournit l'ossature commune ; les sous-classes fournissent les spécificités.
État partagé et constructeurs
Contrairement à une interface traditionnelle, une classe abstraite peut contenir des champs d'instance et définir des constructeurs. Le constructeur ne crée jamais un Animal ou un Shape à lui seul : il s'exécute via super(...) lors de la création d'une sous-classe, en initialisant l'état partagé.
Le champ balance, deposit et applyInterest vivent en un seul endroit. Seule la règle qui varie réellement - interestRate() - est laissée abstraite. Le constructeur d'une sous-classe doit appeler super(...) pour initialiser cet état hérité.
Un piège : mélanger abstract et final
abstract et final sont opposés. Une méthode abstract exige une redéfinition ; une méthode final l'interdit. Marquer la même chose des deux façons - ou rendre une classe abstraite final - provoque une erreur de compilation. N'oubliez pas non plus qu'une classe abstraite peut avoir zéro méthode abstraite : déclarer une classe abstract uniquement pour empêcher son instanciation est légal et parfois utile pour des types de base que vous ne voulez qu'étendre.
abstract final class Bad { } // error: abstract and final conflict
abstract class Base {
abstract final void f(); // error: an abstract method can't be final
}
Classe abstraite contre interface
Elles se recoupent, le choix dépend donc de ce que vous devez partager :
- Classe abstraite - utilisez-la pour des classes étroitement liées qui partagent état et code.
SavingsetCheckingétendent toutes deuxAccount, héritant du champbalanceet de la logique dedeposit. Une classe n'en étend qu'une. - Interface - utilisez-la pour une capacité que des classes non apparentées peuvent partager. Un
Birdet unAirplanepeuvent tous deux êtreFlyablesans partager la moindre implémentation. Une classe peut en implémenter plusieurs.
Un schéma fréquent les combine : une interface définit le contrat, et une classe abstraite implémente le code répétitif afin que les sous-classes concrètes ne complètent que ce qui leur est propre.
Suite : le polymorphisme
Remarquez que dans tous les exemples ci-dessus, nous avons conservé une instance de sous-classe dans une variable du type abstrait, puis appelé une méthode et obtenu automatiquement le comportement de la sous-classe. Cette unique capacité - un seul type de référence, de nombreux comportements à l'exécution - est le polymorphisme, et c'est ce qui rend les classes abstraites et les interfaces vraiment rentables. C'est le sujet de la page suivante.
Questions fréquentes
Qu'est-ce qu'une classe abstraite en Java ?
Une classe abstraite est une classe déclarée avec le mot-clé abstract qui ne peut pas être instanciée telle quelle. Elle est destinée à être étendue. Elle peut mélanger des méthodes et des champs entièrement implémentés (état et code partagés pour les sous-classes) avec des méthodes abstract dépourvues de corps : leur implémentation devient alors la responsabilité de chaque sous-classe.
Peut-on instancier une classe abstraite en Java ?
Non. new AbstractType() provoque une erreur de compilation, car une classe abstraite peut comporter des méthodes non implémentées (abstract), ce qui rendrait l'objet incomplet. Vous instanciez une sous-classe concrète qui fournit toutes les méthodes abstraites, puis vous la stockez dans une variable du type abstrait.
Quelle est la différence entre une classe abstraite et une interface en Java ?
Une classe abstraite peut avoir des champs d'instance, des constructeurs et une logique partiellement implémentée, mais une classe ne peut en étendre qu'une seule. Une interface déclare un comportement sans état d'instance, et une classe peut en implémenter plusieurs. Utilisez une classe abstraite pour partager état et code entre des sous-classes étroitement liées ; utilisez une interface pour doter des classes non apparentées d'une capacité commune.