Java Language
Interfaces
Recherche…
Introduction
interface
. Les interfaces ne peuvent contenir que des constantes, des signatures de méthode, des méthodes par défaut, des méthodes statiques et des types imbriqués. Les corps de méthodes n'existent que pour les méthodes par défaut et les méthodes statiques. Comme les classes abstraites, les interfaces ne peuvent pas être instanciées: elles ne peuvent être implémentées que par des classes ou étendues par d'autres interfaces. L'interface est un moyen courant d'obtenir une abstraction complète en Java.
Syntaxe
- interface publique Foo {void foo (); / * toute autre méthode * /}
- interface publique Foo1 étend Foo {void bar (); / * toute autre méthode * /}
- la classe publique Foo2 implémente Foo, Foo1 {/ * implémentation de Foo et Foo1 * /}
Déclaration et implémentation d'une interface
Déclaration d'une interface utilisant le mot-clé interface
:
public interface Animal {
String getSound(); // Interface methods are public by default
}
Annulation de l'annotation
@Override
public String getSound() {
// Code goes here...
}
Cela oblige le compilateur à vérifier que nous remplaçons et empêche le programme de définir une nouvelle méthode ou de fausser la signature de la méthode.
Les interfaces sont implémentées à l'aide du mot clé implements
.
public class Cat implements Animal {
@Override
public String getSound() {
return "meow";
}
}
public class Dog implements Animal {
@Override
public String getSound() {
return "woof";
}
}
Dans l'exemple, les classes Cat
et Dog
doivent définir la méthode getSound()
car les méthodes d'une interface sont intrinsèquement abstraites (à l'exception des méthodes par défaut).
Utiliser les interfaces
Animal cat = new Cat();
Animal dog = new Dog();
System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"
Implémentation de plusieurs interfaces
Une classe Java peut implémenter plusieurs interfaces.
public interface NoiseMaker {
String noise = "Making Noise"; // interface variables are public static final by default
String makeNoise(); //interface methods are public abstract by default
}
public interface FoodEater {
void eat(Food food);
}
public class Cat implements NoiseMaker, FoodEater {
@Override
public String makeNoise() {
return "meow";
}
@Override
public void eat(Food food) {
System.out.println("meows appreciatively");
}
}
Notez que la classe Cat
doit implémenter les méthodes abstract
héritées dans les deux interfaces. En outre, notez qu'une classe peut pratiquement implémenter autant d'interfaces que nécessaire (la limite de la JVM est limitée à 65 535 ).
NoiseMaker noiseMaker = new Cat(); // Valid
FoodEater foodEater = new Cat(); // Valid
Cat cat = new Cat(); // valid
Cat invalid1 = new NoiseMaker(); // Invalid
Cat invalid2 = new FoodEater(); // Invalid
Remarque:
- Toutes les variables déclarées dans une interface sont
public static final
- Toutes les méthodes déclarées dans une interface sont
public abstract
(cette instruction est valide uniquement via Java 7. A partir de Java 8, vous êtes autorisé à avoir des méthodes dans une interface, qui ne doivent pas nécessairement être abstraites; ces méthodes sont appelées méthodes par défaut ) - Les interfaces ne peuvent être déclarées comme
final
- Si plusieurs interfaces déclarent une méthode ayant une signature identique, elle est effectivement traitée comme une seule méthode et vous ne pouvez pas distinguer la méthode d'interface implémentée
- Un fichier InterfaceName.class correspondant serait généré pour chaque interface, lors de la compilation.
Extension d'une interface
Une interface peut étendre une autre interface via le mot extends
clé extend.
public interface BasicResourceService {
Resource getResource();
}
public interface ExtendedResourceService extends BasicResourceService {
void updateResource(Resource resource);
}
Désormais, une classe implémentant ExtendedResourceService
devra implémenter à la fois getResource()
et updateResource()
.
Extension de plusieurs interfaces
Contrairement aux classes, le mot extends
clé extend peut être utilisé pour étendre plusieurs interfaces (séparées par des virgules), ce qui permet de combiner des interfaces dans une nouvelle interface.
public interface BasicResourceService {
Resource getResource();
}
public interface AlternateResourceService {
Resource getAlternateResource();
}
public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
Resource updateResource(Resource resource);
}
Dans ce cas, une classe implémentant ExtendedResourceService
devra implémenter getResource()
, getAlternateResource()
et updateResource()
.
Utiliser des interfaces avec des génériques
Supposons que vous souhaitiez définir une interface permettant de publier / consommer des données vers et depuis différents types de canaux (par exemple, AMQP, JMS, etc.), mais que vous souhaitiez pouvoir changer les détails d'implémentation ...
Définissons une interface IO de base pouvant être réutilisée sur plusieurs implémentations:
public interface IO<IncomingType, OutgoingType> {
void publish(OutgoingType data);
IncomingType consume();
IncomingType RPCSubmit(OutgoingType data);
}
Maintenant, je peux instancier cette interface, mais comme nous n'avons pas d'implémentations par défaut pour ces méthodes, il faudra une implémentation lorsque nous l'instancierons:
IO<String, String> mockIO = new IO<String, String>() {
private String channel = "somechannel";
@Override
public void publish(String data) {
System.out.println("Publishing " + data + " to " + channel);
}
@Override
public String consume() {
System.out.println("Consuming from " + channel);
return "some useful data";
}
@Override
public String RPCSubmit(String data) {
return "received " + data + " just now ";
}
};
mockIO.consume(); // prints: Consuming from somechannel
mockIO.publish("TestData"); // Publishing TestData to somechannel
System.out.println(mockIO.RPCSubmit("TestData")); // received TestData just now
Nous pouvons aussi faire quelque chose de plus utile avec cette interface, disons que nous voulons l'utiliser pour envelopper certaines fonctions de base de RabbitMQ:
public class RabbitMQ implements IO<String, String> {
private String exchange;
private String queue;
public RabbitMQ(String exchange, String queue){
this.exchange = exchange;
this.queue = queue;
}
@Override
public void publish(String data) {
rabbit.basicPublish(exchange, queue, data.getBytes());
}
@Override
public String consume() {
return rabbit.basicConsume(exchange, queue);
}
@Override
public String RPCSubmit(String data) {
return rabbit.rpcPublish(exchange, queue, data);
}
}
Disons que je veux utiliser cette interface IO maintenant pour compter les visites sur mon site depuis le dernier redémarrage du système et afficher ensuite le nombre total de visites - vous pouvez faire quelque chose comme ceci:
import java.util.concurrent.atomic.AtomicLong;
public class VisitCounter implements IO<Long, Integer> {
private static AtomicLong websiteCounter = new AtomicLong(0);
@Override
public void publish(Integer count) {
websiteCounter.addAndGet(count);
}
@Override
public Long consume() {
return websiteCounter.get();
}
@Override
public Long RPCSubmit(Integer count) {
return websiteCounter.addAndGet(count);
}
}
Maintenant, utilisons le VisitCounter:
VisitCounter counter = new VisitCounter();
// just had 4 visits, yay
counter.publish(4);
// just had another visit, yay
counter.publish(1);
// get data for stats counter
System.out.println(counter.consume()); // prints 5
// show data for stats counter page, but include that as a page view
System.out.println(counter.RPCSubmit(1)); // prints 6
Lorsque vous implémentez plusieurs interfaces, vous ne pouvez pas implémenter la même interface deux fois. Cela s'applique également aux interfaces génériques. Ainsi, le code suivant n'est pas valide et entraînera une erreur de compilation:
interface Printer<T> {
void print(T value);
}
// Invalid!
class SystemPrinter implements Printer<Double>, Printer<Integer> {
@Override public void print(Double d){ System.out.println("Decimal: " + d); }
@Override public void print(Integer i){ System.out.println("Discrete: " + i); }
}
Utilité des interfaces
Les interfaces peuvent être extrêmement utiles dans de nombreux cas. Par exemple, disons que vous avez une liste d’animaux et que vous souhaitez parcourir la liste en imprimant chacun le son qu’ils font.
{cat, dog, bird}
Une façon d'y parvenir serait d'utiliser des interfaces. Cela permettrait d'appeler la même méthode sur toutes les classes
public interface Animal {
public String getSound();
}
Toute classe qui implements Animal
doit également avoir une méthode getSound()
, mais toutes peuvent avoir des implémentations différentes
public class Dog implements Animal {
public String getSound() {
return "Woof";
}
}
public class Cat implements Animal {
public String getSound() {
return "Meow";
}
}
public class Bird implements Animal{
public String getSound() {
return "Chirp";
}
}
Nous avons maintenant trois classes différentes, chacune ayant une méthode getSound()
. Comme toutes ces classes implement
l'interface Animal
, qui déclare la méthode getSound()
, toute instance d'un Animal
peut être appelée sur getSound()
Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();
dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"
Parce que chacun de ces Animal
est un Animal
, nous pourrions même mettre les animaux dans une liste, les parcourir en boucle et imprimer leurs sons.
Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
System.out.println(animal.getSound());
}
Comme l'ordre du tableau est Dog
, Cat
, puis Bird
, "Woof Meow Chirp" sera imprimé sur la console.
Les interfaces peuvent également être utilisées comme valeur de retour pour les fonctions. Par exemple, retourner un Dog
si l'entrée est "chien" , Cat
si l'entrée est "chat" , et Bird
si c'est "oiseau" , et imprimer le son de cet animal pourrait être fait en utilisant
public Animal getAnimalByName(String name) {
switch(name.toLowerCase()) {
case "dog":
return new Dog();
case "cat":
return new Cat();
case "bird":
return new Bird();
default:
return null;
}
}
public String getAnimalSoundByName(String name){
Animal animal = getAnimalByName(name);
if (animal == null) {
return null;
} else {
return animal.getSound();
}
}
String dogSound = getAnimalSoundByName("dog"); // "Woof"
String catSound = getAnimalSoundByName("cat"); // "Meow"
String birdSound = getAnimalSoundByName("bird"); // "Chirp"
String lightbulbSound = getAnimalSoundByName("lightbulb"); // null
Les interfaces sont également utiles pour l'extensibilité, car si vous souhaitez ajouter un nouveau type d' Animal
, vous n'avez rien à changer avec les opérations que vous effectuez.
Implémentation d'interfaces dans une classe abstraite
Une méthode définie dans une interface
est par défaut public abstract
. Lorsqu'une abstract class
implémente une interface
, toutes les méthodes définies dans l' interface
ne doivent pas être implémentées par la abstract class
. En effet, une class
déclarée abstract
peut contenir des déclarations de méthode abstraites. Il incombe donc à la première sous-classe concrète d’implémenter toutes abstract
méthodes abstract
héritées des interfaces et / ou de la abstract class
.
public interface NoiseMaker {
void makeNoise();
}
public abstract class Animal implements NoiseMaker {
//Does not need to declare or implement makeNoise()
public abstract void eat();
}
//Because Dog is concrete, it must define both makeNoise() and eat()
public class Dog extends Animal {
@Override
public void makeNoise() {
System.out.println("Borf borf");
}
@Override
public void eat() {
System.out.println("Dog eats some kibble.");
}
}
A partir de Java 8 en avant , il est possible pour une interface
de déclarer par default
les implémentations de méthodes qui signifie que la méthode ne sera pas abstract
, donc des sous-classes concrètes ne seront pas contraints de mettre en œuvre la méthode , mais héritera de la default
la mise en œuvre , sauf contrordre.
Méthodes par défaut
Introduites dans Java 8, les méthodes par défaut permettent de spécifier une implémentation dans une interface. Cela pourrait être utilisé pour éviter la classe typique "Base" ou "Abstract" en fournissant une implémentation partielle d'une interface et en limitant la hiérarchie des sous-classes.
Implémentation du modèle d'observateur
Par exemple, il est possible d'implémenter le modèle Observer-Listener directement dans l'interface, offrant plus de flexibilité aux classes d'implémentation.
interface Observer {
void onAction(String a);
}
interface Observable{
public abstract List<Observer> getObservers();
public default void addObserver(Observer o){
getObservers().add(o);
}
public default void notify(String something ){
for( Observer l : getObservers() ){
l.onAction(something);
}
}
}
Maintenant, n'importe quelle classe peut être rendue "Observable" simplement en implémentant l'interface Observable, tout en étant libre de faire partie d'une hiérarchie de classes différente.
abstract class Worker{
public abstract void work();
}
public class MyWorker extends Worker implements Observable {
private List<Observer> myObservers = new ArrayList<Observer>();
@Override
public List<Observer> getObservers() {
return myObservers;
}
@Override
public void work(){
notify("Started work");
// Code goes here...
notify("Completed work");
}
public static void main(String[] args) {
MyWorker w = new MyWorker();
w.addListener(new Observer() {
@Override
public void onAction(String a) {
System.out.println(a + " (" + new Date() + ")");
}
});
w.work();
}
}
Problème de diamant
Le compilateur Java 8 est conscient du problème de diamant qui se produit lorsqu'une classe implémente des interfaces contenant une méthode avec la même signature.
Pour le résoudre, une classe d'implémentation doit remplacer la méthode partagée et fournir sa propre implémentation.
interface InterfaceA {
public default String getName(){
return "a";
}
}
interface InterfaceB {
public default String getName(){
return "b";
}
}
public class ImpClass implements InterfaceA, InterfaceB {
@Override
public String getName() {
//Must provide its own implementation
return InterfaceA.super.getName() + InterfaceB.super.getName();
}
public static void main(String[] args) {
ImpClass c = new ImpClass();
System.out.println( c.getName() ); // Prints "ab"
System.out.println( ((InterfaceA)c).getName() ); // Prints "ab"
System.out.println( ((InterfaceB)c).getName() ); // Prints "ab"
}
}
Il y a toujours le problème d'avoir des méthodes avec le même nom et les mêmes paramètres avec des types de retour différents, qui ne compileront pas.
Utiliser les méthodes par défaut pour résoudre les problèmes de compatibilité
Les implémentations de méthodes par défaut sont très pratiques si une méthode est ajoutée à une interface dans un système existant où les interfaces sont utilisées par plusieurs classes.
Pour éviter de casser le système entier, vous pouvez fournir une implémentation de méthode par défaut lorsque vous ajoutez une méthode à une interface. De cette façon, le système continuera à compiler et les implémentations réelles pourront être effectuées étape par étape.
Pour plus d'informations, voir la rubrique Méthodes par défaut .
Modificateurs dans les interfaces
Le Oracle Java Style Guide indique:
Les modificateurs ne doivent pas être écrits lorsqu'ils sont implicites.
(Voir Modifiers dans Oracle Official Code Standard pour le contexte et un lien vers le document Oracle réel.)
Ce guide de style s'applique particulièrement aux interfaces. Considérons l'extrait de code suivant:
interface I {
public static final int VARIABLE = 0;
public abstract void method();
public static void staticMethod() { ... }
public default void defaultMethod() { ... }
}
Les variables
Toutes les variables d'interface sont des constantes implicites avec des modificateurs implicites public
(accessibles pour tous), static
(accessibles par nom d'interface) et final
(doivent être initialisées pendant la déclaration):
public static final int VARIABLE = 0;
Les méthodes
- Toutes les méthodes qui ne fournissent pas d'implémentation sont implicitement
public
etabstract
.
public abstract void method();
- Toutes les méthodes avec
static
modificateurstatic
oudefault
doivent fournir une implémentation et sont implicitementpublic
.
public static void staticMethod() { ... }
Une fois que toutes les modifications ci-dessus auront été appliquées, nous obtiendrons ce qui suit:
interface I {
int VARIABLE = 0;
void method();
static void staticMethod() { ... }
default void defaultMethod() { ... }
}
Renforcer les paramètres de type borné
Les paramètres de type lié vous permettent de définir des restrictions sur les arguments de type générique:
class SomeClass {
}
class Demo<T extends SomeClass> {
}
Mais un paramètre de type ne peut être lié qu'à un seul type de classe.
Un type d'interface peut être lié à un type qui a déjà une liaison. Ceci est réalisé en utilisant le symbole &
:
interface SomeInterface {
}
class GenericClass<T extends SomeClass & SomeInterface> {
}
Cela renforce la liaison, nécessitant potentiellement des arguments de type à dériver de plusieurs types.
Plusieurs types d'interface peuvent être liés à un paramètre de type:
class Demo<T extends SomeClass & FirstInterface & SecondInterface> {
}
Mais devrait être utilisé avec prudence. Les liaisons d'interface multiples sont généralement un signe d' odeur de code , ce qui suggère qu'un nouveau type doit être créé, qui agit comme un adaptateur pour les autres types:
interface NewInterface extends FirstInterface, SecondInterface {
}
class Demo<T extends SomeClass & NewInterface> {
}