Bonjour! Aujourd'hui, nous allons parler d'un concept important en Java : les interfaces. Le mot vous est probablement familier. Par exemple, la plupart des programmes informatiques et des jeux ont des interfaces. Au sens large, une interface est une sorte de « télécommande » qui relie deux parties interagissant entre elles. Un exemple simple d’interface de la vie quotidienne est une télécommande de téléviseur. Il connecte deux objets, une personne et un téléviseur, et effectue différentes tâches : augmenter ou diminuer le volume, changer de chaîne, allumer ou éteindre le téléviseur. Un côté (la personne) doit accéder à l'interface (appuyer sur le bouton de la télécommande) pour que l'autre côté puisse effectuer l'action. Par exemple, pour que le téléviseur passe à la chaîne suivante. Dans ce cas, l'utilisateur n'a pas besoin de connaître l'appareil du téléviseur et comment le processus de changement de chaîne est mis en œuvre à l'intérieur. Tout ce à quoi l'utilisateur a accès est l'interface . La tâche principale est d'obtenir le résultat souhaité. Quel est le rapport avec la programmation et Java ? Direct :) La création d'une interface est très similaire à la création d'une classe normale, mais au lieu du mot,
class
nous spécifions le mot interface
. Examinons l'interface Java la plus simple et voyons comment elle fonctionne et à quoi elle sert :
public interface Swimmable {
public void swim();
}
Nous avons créé une interface Swimmable
qui sait nager . C'est un peu comme notre télécommande, qui a un « bouton » : la méthode swim()
est « nager ». Comment peut-on utiliser cette « télécommande » ? A cet effet, la méthode, c'est-à-dire Le bouton de notre télécommande doit être implémenté. Pour utiliser une interface, ses méthodes doivent être implémentées par certaines classes de notre programme. Imaginons une classe dont les objets correspondent à la description « sait nager ». Par exemple, la classe canard convient Duck
:
public class Duck implements Swimmable {
public void swim() {
System.out.println("Duck, swim!");
}
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
}
}
Que voit-on ici ? Une classe Duck
est associée à une interface Swimmable
à l'aide du mot-clé implements
. Si vous vous en souvenez, nous avons utilisé un mécanisme similaire pour connecter deux classes en héritage, seulement il y avait le mot « extends ». «public class Duck implements Swimmable
» peut être traduit littéralement pour plus de clarté : « une classe publique Duck
implémente l'interfaceSwimmable
». Cela signifie qu'une classe associée à une interface doit implémenter toutes ses méthodes. Attention : dans notre classe, Duck
tout comme dans l'interface , Swimmable
il y a une méthode swim()
, et à l'intérieur il y a une sorte de logique. Il s’agit d’une exigence obligatoire. Si nous écrivions simplement «public class Duck implements Swimmable
» et ne créions pas de méthode swim()
dans la classe Duck
, le compilateur nous donnerait une erreur : Duck n'est pas abstrait et ne remplace pas la méthode abstraite swim() dans Swimmable. Pourquoi cela se produit-il ? Si nous expliquons l'erreur en utilisant l'exemple d'un téléviseur, il s'avère que nous donnons à une personne une télécommande avec un bouton « changer de chaîne » d'un téléviseur qui ne sait pas changer de chaîne. A ce stade, appuyez autant que vous le souhaitez sur le bouton, rien ne fonctionnera. La télécommande elle-même ne change pas de chaîne : elle donne seulement un signal au téléviseur, à l'intérieur duquel est mis en œuvre un processus complexe de changement de chaîne. Il en va de même pour notre canard : il doit être capable de nager pour pouvoir y accéder via l'interface Swimmable
. Si elle ne sait pas comment procéder, l'interface Swimmable
ne reliera pas les deux parties : la personne et le programme. Une personne ne pourra pas utiliser une méthode swim()
pour faire Duck
flotter un objet dans un programme. Vous avez maintenant vu plus clairement à quoi servent les interfaces. Une interface décrit le comportement que doivent avoir les classes qui implémentent cette interface. Le « comportement » est un ensemble de méthodes. Si nous voulons créer plusieurs messagers, le moyen le plus simple de le faire est de créer une interface Messenger
. Que devrait être capable de faire n’importe quel messager ? Sous une forme simplifiée, recevez et envoyez des messages.
public interface Messenger{
public void sendMessage();
public void getMessage();
}
Et maintenant nous pouvons simplement créer nos classes de messagerie en implémentant cette interface. Le compilateur lui-même nous « forcera » à les implémenter dans les classes. Télégramme:
public class Telegram implements Messenger {
public void sendMessage() {
System.out.println("Sending a message to Telegram!");
}
public void getMessage() {
System.out.println("Reading the message in Telegram!");
}
}
WhatsApp :
public class WhatsApp implements Messenger {
public void sendMessage() {
System.out.println("Sending a WhatsApp message!");
}
public void getMessage() {
System.out.println("Reading a WhatsApp message!");
}
}
Viber :
public class Viber implements Messenger {
public void sendMessage() {
System.out.println("Sending a message to Viber!");
}
public void getMessage() {
System.out.println("Reading a message in Viber!");
}
}
Quels avantages cela apporte-t-il ? Le plus important d’entre eux est le couplage lâche. Imaginez que nous concevons un programme dans lequel nous collecterons des données clients. La classe Client
doit avoir un champ indiquant quel messager le client utilise. Sans interfaces, cela semblerait étrange :
public class Client {
private WhatsApp whatsApp;
private Telegram telegram;
private Viber viber;
}
Nous avons créé trois champs, mais un client peut facilement n'avoir qu'un seul messager. Nous ne savons tout simplement pas lequel. Et pour ne pas se retrouver sans communication avec le client, il faut « pousser » toutes les options possibles dans la classe. Il s'avère qu'un ou deux d'entre eux seront toujours là null
et qu'ils ne sont pas du tout nécessaires au fonctionnement du programme. Il est préférable d'utiliser notre interface :
public class Client {
private Messenger messenger;
}
Ceci est un exemple de « couplage lâche » ! Au lieu de spécifier une classe de messagerie spécifique dans la classe Client
, nous mentionnons simplement que le client dispose d'un messager. Lequel sera déterminé au cours du programme. Mais pourquoi avons-nous besoin d’interfaces pour cela ? Pourquoi ont-ils été ajoutés à la langue ? La question est bonne et correcte ! Le même résultat peut être obtenu en utilisant l’héritage ordinaire, n’est-ce pas ? La classe Messenger
est la classe parente, et Viber
, Telegram
et WhatsApp
sont les héritiers. En effet, il est possible de le faire. Mais il y a un problème. Comme vous le savez déjà, il n’existe pas d’héritage multiple en Java. Mais il existe plusieurs implémentations d’interfaces. Une classe peut implémenter autant d’interfaces qu’elle le souhaite. Imaginez que nous ayons une classe Smartphone
qui possède un champ Application
- une application installée sur un smartphone.
public class Smartphone {
private Application application;
}
L'application et le messager sont bien sûr similaires, mais ce sont quand même des choses différentes. Messenger peut être à la fois mobile et de bureau, tandis qu'Application est une application mobile. Donc, si nous utilisions l'héritage, nous ne pourrions pas ajouter d'objet Telegram
à la classe Smartphone
. Après tout, une classe Telegram
ne peut pas hériter de Application
et de Messenger
! Et nous avons déjà réussi à en hériter Messenger
et à l'ajouter à la classe sous cette forme Client
. Mais une classe Telegram
peut facilement implémenter les deux interfaces ! Par conséquent, dans une classe, Client
nous pouvons implémenter un objet Telegram
comme Messenger
, et dans une classe Smartphone
comme Application
. Voici comment procéder :
public class Telegram implements Application, Messenger {
//...methods
}
public class Client {
private Messenger messenger;
public Client() {
this.messenger = new Telegram();
}
}
public class Smartphone {
private Application application;
public Smartphone() {
this.application = new Telegram();
}
}
Nous pouvons maintenant utiliser la classe Telegram
à notre guise. Quelque part, il agira dans le rôle de Application
, quelque part dans le rôle de Messenger
. Vous avez probablement déjà remarqué que les méthodes dans les interfaces sont toujours « vides », c'est-à-dire qu'elles n'ont aucune implémentation. La raison en est simple : une interface décrit le comportement, pas l’implémente. « Tous les objets des classes qui implémentent l’interface Swimmable
doivent pouvoir flotter » : c’est tout ce que nous dit l’interface. Comment exactement un poisson, un canard ou un cheval nagera, c'est une question pour les classes Fish
, Duck
et Horse
, et non pour l'interface. Tout comme changer de chaîne est la tâche d'un téléviseur. La télécommande vous donne simplement un bouton pour le faire. Cependant, Java8 a un ajout intéressant : les méthodes par défaut. Par exemple, votre interface dispose de 10 méthodes. 9 d'entre eux sont implémentés différemment dans différentes classes, mais un est implémenté de la même manière dans toutes. Auparavant, avant la sortie de Java8, les méthodes à l'intérieur des interfaces n'avaient aucune implémentation : le compilateur renvoyait immédiatement une erreur. Maintenant, vous pouvez procéder comme ceci :
public interface Swimmable {
public default void swim() {
System.out.println("Swim!");
}
public void eat();
public void run();
}
À l'aide du mot-clé default
, nous avons créé une méthode dans l'interface avec une implémentation par défaut. Nous devrons implémenter nous -mêmes les deux autres méthodes, eat()
et run()
, dans toutes les classes qui implémenteront Swimmable
. Il n'est pas nécessaire de faire cela avec la méthode swim()
: l'implémentation sera la même dans toutes les classes. À propos, vous avez rencontré des interfaces plus d'une fois dans des tâches antérieures, même si vous ne l'avez pas remarqué vous-même :) Voici un exemple évident : vous avez travaillé avec des interfaces List
et Set
! Plus précisément, avec leurs implémentations - ArrayList
, LinkedList
, HashSet
et autres. Le même diagramme montre un exemple lorsqu'une classe implémente plusieurs interfaces à la fois. Par exemple, LinkedList
il implémente les interfaces List
et Deque
(file d'attente double face). Vous connaissez également l'interface Map
, ou plutôt ses implémentations - HashMap
. À propos, dans ce diagramme, vous pouvez voir une fonctionnalité : les interfaces peuvent être héritées les unes des autres. L'interface SortedMap
est héritée de Map
, et Deque
est héritée de file d'attente Queue
. Ceci est nécessaire si vous souhaitez afficher la connexion entre les interfaces, mais qu’une interface est une version étendue d’une autre. Regardons un exemple avec une interface Queue
- une file d'attente. Nous n'avons pas encore parcouru les collections Queue
, mais elles sont assez simples et disposées comme une file d'attente régulière dans un magasin. Vous pouvez ajouter des éléments uniquement à la fin de la file d'attente et les supprimer uniquement depuis le début. À un certain stade, les développeurs avaient besoin d'une version étendue de la file d'attente afin que des éléments puissent être ajoutés et reçus des deux côtés. C'est ainsi qu'une interface a été créée Deque
: une file d'attente bidirectionnelle. Elle contient toutes les méthodes d'une file d'attente normale, car elle est le « parent » d'une file d'attente bidirectionnelle, mais de nouvelles méthodes ont été ajoutées.
GO TO FULL VERSION