Différences entre Interfaces et Classes Abstraites en Java

Différences fondamentales entre Interfaces et Classes Abstraites

  1. Classes Abstraites

Une classe abstraite permet de modéliser des entités partageant des caractéristiques communes, tant au niveau des attributs que des comportements. Prenons l'exemple de différents types de véhicules :

public abstract class Vehicule {
  double poids;
  double vitesseMax;
  String couleur;

  abstract void deplacer();

  abstract void freiner();

  void klaxonner() {
    System.out.println("Le véhicule klaxonne");
  }
}

À partir de cet exemple, nous pouvons tirer notre première conclusion : une classe abstraite peut contenir à la fois des méthodes abstraites et des méthodes concrètes.

Si nous essayons de créer une classe Voiture qui hérite de Vehicule sans implémenter les méthodes abstraites, le compilateur génère une erreur. Nous devrons soit déclarer Voiture comme abstraite, soit implémenter toutes les méthodes abstraites de Vehicule.

Cela nous amène à notre deuxième conclusion : lorsqu'une sous-classe hérite d'une classe abstraite, elle doit nécessairement implémenter toutes les méthodes abstraites de la classe parente.

Une question légitime se pose : si une classe abstraite ne peut pas être instanciée, quel est l'intérêt des méthodes concrètes qu'elle contient ?

Leur rôle est d'améliorer la réutilisabilité du code ! Dans notre exemple, tous les véhicules klaxonnent de la même manière par défaut. Les sous-classes n'ont donc pas besoin de redéfinir cette fonctionnalité.

  1. Interfaces

Bien que les classes abstraites soient puissantes pour modéliser des entités avec des attributs et des comportements communs, elles présentent un inconvénient majeur : un couplement élevé.

Considérons un exemple avec une classe Oiseau et une classe Avion. Comment modéliser leur capacité à voler ? Si nous créons une classe abstraite Volant avec une méthode abstract void voler(), un oiseau ne pourra pas hériter à la fois de Animal et de Volant en raison de l'héritage simple en Java.

Les interfaces résolvent ce problème en ne modélisant que des comportements. Java autorise l'héritage simple mais l'implémentation multiple d'interfaces.

Nous pouvons définir une interface CapableDeVoler :

public interface CapableDeVoler {
  void voler();
}

Pour les entités capables de nager, nous pourrions créer une interface CapableDeNager :

public interface CapableDeNager {
  void nager();
}

Maintenant, un Oiseau peut hériter de la classe Animal et implémenter l'interface CapableDeVoler. De même, un Avion peut implémenter CapableDeVoler. Une Grenouille peut hériter d'Animal et implémenter CapableDeNager, tout comme un NageurOlympique.

Cela nous conduit à notre troisième conclusion : les classes abstraites modélisent des entités avec des attributs et comportements communs, tandis que les interfaces ne modélisent que des comportements.

  1. Méthodes dans les Interfaces

Avant Java 8, les inetrfaces ne contenaient que des méthodes abstraites. Si nous essayons de créer une classe Oiseau implémentant CapableDeVoler sans implémenter la méthode voler(), le compilateur génère une erreur.

Cela confirme notre quatrième conclusion : les méthodes des interfaces sont par défaut abstraites et doivent être implémentées par les classes qui utilisent l'interface.

Avec Java 8, deux types de méthodes ont été ajoutés aux interfaces : les méthodes default et les méthodes static.

Méthodes default :

public interface CapableDeVoler {
  void voler();

  default void tester() {
    System.out.println("Test de méthode default");
  }
}

Après implémentation, le compilateur ne génère plus d'erreur, indiquant que les sous-classes ne sont pas forcées d'implémenter cette méthode. Le rôle des méthodes default est similaire à celui des méthodes concrètes dans les classes abstraites : améliorer la réutilisabilité du code.

Notre cinquième conclusion : les méthodes default des interfaces ne nécessitent pas d'implémentation par les sous-classes.

Méthodes static :

public interface CapableDeVoler {
  void voler();

  default void tester() {
    System.out.println("Test de méthode default");
  }

  static void testerStatique() {
    System.out.println("Test de méthode statique");
  }
}

Les sous-classes n'ont pas besoin d'implémenter les méthodes statiques, qui sont appelées directement via le nom de l'interface : CapableDeVoler.testerStatique().

Notre sixième conclusion : les méthodes static des interfaces ne nécessitent pas d'implémentation par les sous-classes et sont appelées via le nom de l'interface.

Les interfaces autorisent également des champs statiques, qui sont par défaut public static final et ne peuvent être modifiés par les classes implémentant l'interface.

  1. Synthèse des différences

En combinant nos conclusions, nous pouvons résumer les différences clés entre interfaces et classes abstraites :

  1. Les classes abstraites modélisent des entités avec des attributs et comportements communs, tandis que les interfaces ne modélisent que des comportements.
  2. Les classes abstraites peuvent contenir des méthodes abstraites et des méthodes concrètes.
  3. Lorsqu'une sous-classe hérite d'une classe abstraite, elle doit nécessairement implémenter toutes les méthodes abstraites.
  4. Les méthodes des interfaces sont par défaut abstraites et doivent être implémentées par les classes qui utilisent l'interface.
  5. Les méthodes default et static des interfaces ne nécessitent pas d'implémentation par les sous-classes.

Ces explications, bien que simplifiées, sont basées sur les fonctionnalités de Java 8. Il est recommandé de tester ces concepts par vous-même pour mieux les comprendre et les maîtriser.

Étiquettes: Java POO interfaces classes-abstraites JDK-8

Publié le 18 juin à 18h04