Vous êtes sur la page 1sur 19

Java Interface

Dans ce cours, nous allons découvrir les interfaces Java. Nous apprendrons comment définir une
Interface, comment l’implémenter et quand l’utiliser.
Une interface peut être considéré comme une « classe » entièrement abstraite. Il est constitué
d’ un groupe de méthodes abstraites (méthodes sans corps).
Pour créer une interface en Java, nous utilisons le mot clé interface comme le montre l’exemple
ci-dessous
interface Language {
public void getType();
public void getVersion();
}
• Language est une interface.
• Il inclut les méthodes abstraites : getType() getVersion().
Implémentation d’une interface
Comme les classes abstraites, nous ne pouvons pas créer d'objets instances d'interfaces.
Pour utiliser une interface, il doit être implémenté par au moins une classe. Nous utilisons le
mot clé implements pour implémenter une interface.
Example 1: Java Interface
interface Polygon {
void getArea(int length, int breadth);
}
// implement the Polygon interface
class Rectangle implements Polygon {

// implementation of abstract method


public void getArea(int length, int breadth) {
System.out.println("The area of the rectangle is " + (length *
breadth));
}
}
class Main {
public static void main(String[] args) {
Rectangle r1 = new Rectangle();
r1.getArea(5, 6);
}
}
Exécution:
The area of the rectangle is 30
Example 2: Java Interface
// create an interface
interface Language {
void getName(String name);
}
// class implements interface
class ProgrammingLanguage implements Language {

// implementation of abstract method


public void getName(String name) {
System.out.println("Programming Language: " + name);
}
}

class Main {
public static void main(String[] args) {
ProgrammingLanguage language = new ProgrammingLanguage();
language.getName("Java");
}
}
Exécution

Programming Language: Java

Implementation de plusieurs Interfaces


En Java, une classe peut implémenter plusieurs interfaces come dans l’exemple ci-dessous.
interface A {
// members of A
}

interface B {
// members of B
}
class C implements A, B {
// abstract members of A
// abstract members of B
}
Héritage d’Interface
Semblables aux classes, les interfaces peuvent étendre d'autres interfaces. Le mot clé extend est
utilisé pour étendre les interfaces. Par example,
interface Line {
// members of Line interface
}

// extending interface
interface Polygon extends Line {
// members of Polygon interfac
}
Dans cet exemple l'interface Polygon étend l'interface Line. Par conséquent Maintenant, si une
classe implémente Polygon, elle devrait fournir des implémentations pour toutes les méthodes
abstraites de Line et Polygon.
Extension de plusieurs interfaces : Héritage multiple
Une interface peut étendre (ou hériter de) plusieurs interfaces. Par exemple,
interface A {
//A Members
}
interface B {
//B Members
}

interface C extends A, B {
//C Members
}

Avantages des Interfaces


Les principaux avantages des interfaces sont :

• Les interfaces fournissent des spécifications qu'une classe (qui l'implémente) doit suivre.
• Semblables aux classes abstraites, les interfaces aident à réaliser l'abstraction en Java.
Par exemple la méthode getArea() nous indique qu’il est possible de calculer la surface
des polygones, mais la façon dont la surface est calculée est différente d’un polygone à
l’autre. Par conséquent, l'implémentation de getArea() est indépendante l'une de l'autre.
• Les interfaces sont également utilisées pour réaliser le polymorphisme avec plusieurs
• Remarque : toutes les méthodes à l'intérieur d'une interface sont implicitement public et
tous les champs sont implicitement public static final.
Par example,
interface Language {
// by default public static final
String type = "programming language";

// by default public
void getName();
}
interface Language {
// by default public static final
public static final String type = "programming language";

// by default public
public void getName();
}

Méthodes par défaut dans une interface


Depuis la version 8 de Java, il est possible d’implémenter une méthode dans une interface. De
telles méthodes sont appelées méthodes par défaut.
Pour déclarer les méthodes par défaut à l'intérieur d’une interface, nous utilisons le mot-clé
default. Par exemple,
public interface I1 {
public default void getSides() {
// body of getSides()
}
…….
}
Pourquoi les méthodes par défaut?
La raison pour laquelle la version Java 8 a inclus des méthodes par défaut est assez évidente.
Dans une conception typique basée sur des abstractions, où une interface a une ou plusieurs
implémentations, si une ou plusieurs méthodes sont ajoutées à l'interface, toutes les
implémentations seront obligées de les implémenter également. Sinon, la conception
s'effondrera tout simplement.
Les méthodes d'interface par défaut sont un moyen efficace de résoudre ce problème. Ils nous
permettent d'ajouter de nouvelles méthodes à une interface qui sont automatiquement
disponibles dans les implémentations. Par conséquent, nous n'avons pas besoin de modifier les
classes d'implémentation. De cette façon, la compatibilité descendante est soigneusement
préservée.
Prenons un exemple pour mieux comprendre les méthodes par défaut.
interface Polygon {
void getArea();

// default method
default void getSides() {
System.out.println("I can get sides of a polygon.");
}
}
// implements the interface
class Rectangle implements Polygon {
public void getArea() {
int length = 6;
int breadth = 5;
int area = length * breadth;
System.out.println("The area of the rectangle is " + area);
}

// overrides the getSides()


public void getSides() {
System.out.println("I have 4 sides.");
}
}
// implements the interface
class Square implements Polygon {
public void getArea() {
int length = 5;
int area = length * length;
System.out.println("The area of the square is " + area);
}
}
public class Main {
public static void main(String[] args) {

// create an object of Rectangle


Rectangle r1 = new Rectangle();
r1.getArea();
r1.getSides();

// create an object of Square


Square s1 = new Square();
s1.getArea();
s1.getSides();
}
}
Exécution:
The area of the rectangle is 30
I have 4 sides.
The area of the square is 25
I can get sides of a polygon.
Dans l'exemple ci-dessus, nous avons créé une interface nommée Polygon qui a une méthode
par défaut getSides() et une méthode abstraite getArea().
Nous avons créé deux classes Rectangle et Square qui implémentent Polygon.
La classe Rectangle fournit l'implémentation de la méthode getArea() et remplace (Override) la
méthode getSides(). Cependant, la classe Square ne fournit que l'implémentation de la méthode
getArea().
Désormais, lors de l'appel de la méthode getSides() à l'aide de l'objet Rectangle, la méthode
redéfinie est appelée. Cependant, dans le cas de l'objet Square, la méthode par défaut est
appelée.
Autre exemple de méthodes par défaut
Supposons que nous ayons une interface Vehicle implémentée par une seule classe. Il pourrait y
en avoir plus, bien sûr :
Interface Vehicle
public interface Vehicle {
String getBrand();
String speedUp();
String slowDown();

default String turnAlarmOn() {


return "Turning the vehicle alarm on.";
}

default String turnAlarmOff() {


return "Turning the vehicle alarm off.";
}
}
Classe qui implémente l’interface Vehicle
public class Car implements Vehicle {
private String brand;

public Car(String brand) {


this.brand = brand;
}
@Override
public String getBrand() {
return brand;
}
@Override
public String speedUp() {
return "The car is speeding up.";
}

@Override
public String slowDown() {
return "The car is slowing down.";
}
}

Classe Main
public class Main {
public static void main(String[] args) {
Vehicle car = new Car("BMW");
System.out.println(car.getBrand());
System.out.println(car.speedUp());
System.out.println(car.slowDown());
System.out.println(car.turnAlarmOn());
System.out.println(car.turnAlarmOff());
}

}
Exécution:
BMW
The car is speeding up.
The car is slowing down.
Turning the vehicle alarm on.
Turning the vehicle alarm off.

Noter que les méthodes par défaut, turnAlarmOn() et turnAlarmOff(), de l’interface Vehicle sont
automatiquement disponibles dans la classe Car qui l’implémente.
De plus, si à un moment donné nous décidons d'ajouter plus de méthodes par défaut à
l'interface Vehicle, l'application continuera à fonctionner et nous n'aurons pas à forcer la classe
à fournir des implémentations pour les nouvelles méthodes.
L'utilisation la plus courante des méthodes par défaut de l'interface consiste à fournir de
manière incrémentielle des fonctionnalités supplémentaires à une interface donné sans mettre
hors service les classes qui l’implémentent.
Règles d'héritage d'interfaces multiples

Les méthodes d'interface par défaut sont une fonctionnalité assez intéressante, mais certaines
mises en garde méritent d'être mentionnées. Étant donné que Java permet aux classes
d'implémenter plusieurs interfaces, il est important de savoir ce qui se passe lorsqu'une classe
implémente plusieurs interfaces qui définissent les mêmes méthodes par défaut.
Pour mieux comprendre ce scénario, définissons une nouvelle interface Alarme et redéfinissons
la classe Car :
public interface Alarm {
default String turnAlarmOn() {
return "Turning the alarm on.";
}

default String turnAlarmOff() {


return "Turning the alarm off.";
}
}
public class Car implements Vehicle, Alarm {
……
}

La classe Car implémente les deux interfaces Vehicle, et Alarm qui contiennent des fonctions par
défaut identiques. Le code ne sera tout simplement pas compilé, car il y a un conflit causé par
l'héritage d'interfaces multiples (Diamond Problem). La classe Car hériterait des deux ensembles
de méthodes par défaut. Alors, lesquels devons-nous appeler ?
Pour lever cette ambiguïté, il faut explicitement fournir une implémentation pour les méthodes
:

@Override
public String turnAlarmOn() {
// custom implementation
return "";
}

@Override
public String turnAlarmOff() {
// custom implementation
return "";
}

Nous pouvons également faire en sorte que notre classe utilise les méthodes par défaut de l'une
des interfaces.
Voyons un exemple qui utilise les méthodes par défaut de l'interface Vehicle :

@Override
public String turnAlarmOn() {
return Vehicle.super.turnAlarmOn();
}
@Override
public String turnAlarmOff() {
return Vehicle.super.turnAlarmOff();
}

De même, nous pouvons faire en sorte que la classe Car utilise les méthodes par défaut définies
dans l'interface Alarm :
@Override
public String turnAlarmOn() {
return Alarm.super.turnAlarmOn();
}

@Override
public String turnAlarmOff() {
return Alarm.super.turnAlarmOff();
}

Il est même possible de faire en sorte que la classe Car utilise les deux ensembles de méthodes
par défaut :
@Override
public String turnAlarmOn() {
return Vehicle.super.turnAlarmOn() + " " +
Alarm.super.turnAlarmOn();
}

@Override
public String turnAlarmOff() {
return Vehicle.super.turnAlarmOff() + " " +
Alarm.super.turnAlarmOff();
}

Méthodes private et static dans une Interface


Java 8 a également ajouté une autre fonctionnalité pour inclure des méthodes statiques dans
une interface.
Pour utiliser une méthode statique d’interface, nous utilisons le nom de l’interface de la même
manière que ce que nous faisons avec une classe.
Exemple:
interface MyInterface {
public static void staticMethod(){
System.out.println("I am a static method");
};
}

class TestMyInterace implements MyInterface{


// access static method
public static void main(String[] args) {
MyInterface.staticMethod();
}
Note: Avec la sortie de Java 9, les méthodes privées sont également prises en charge dans les
interfaces.
Nous ne pouvons pas créer des objets d'une interface. Par conséquent, les méthodes privées
sont utilisées comme méthodes d'aide (Helper methods) pour d'autres méthodes public de
l’interface.
Exemple pratique d’utilisation d'interface
// To use the sqrt function
import java.lang.Math;

interface Polygon {
void getArea();

// calculate the perimeter of a Polygon


default void getPerimeter(int... sides) {
int perimeter = 0;
for (int side: sides) {
perimeter += side;
}

System.out.println("Perimeter: " + perimeter);


}
}
class Triangle implements Polygon {
private int a, b, c;
private double s, area;

// initializing sides of a triangle


Triangle(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
s = 0;
}

// calculate the area of a triangle


public void getArea() {
s = (double) (a + b + c)/2;
area = Math.sqrt(s*(s-a)*(s-b)*(s-c));
System.out.println("Area: " + area);
}
}
class Main {
public static void main(String[] args) {
Triangle t1 = new Triangle(2, 3, 4);

// calls the method of the Triangle class


t1.getArea();

// calls the method of Polygon


t1.getPerimeter(2, 3, 4);
}
}
Exécution:
Area: 2.9047375096555625
Perimeter: 9

Dans le programme ci-dessus, nous avons créé une interface nommée Polygon. Il comprend une
méthode par défaut getPerimeter() et une méthode abstraite getArea().
Nous pouvons calculer le périmètre de tous les polygones de la même manière, nous avons
donc implémenté le corps de getPerimeter() dans Polygon.
Désormais, tous les polygones qui implémentent Polygon peuvent utiliser getPerimeter() pour
calculer le périmètre.
Cependant, la règle de calcul de l'aire est différente pour différents polygones. Par conséquent,
getArea() est inclus sans implémentation.
Toute classe qui implémente Polygon doit fournir une implémentation de getArea().
Lambda Expressions en Java
Lambda Expression a été introduite pour la première fois dans Java 8. Son objectif principal est
d'augmenter le pouvoir expressif du langage.
Mais, avant de présenter qu’est-ce qu’une Lambda Expression, nous devons d'abord
comprendre les interfaces fonctionnelles.

Qu’est-ce qu’une Interface Fonctionnelle ( Functional Interface)?

Si une interface Java contient une et une seule méthode abstraite, alors il est appelé interface
fonctionnelle. Cette seule méthode spécifie le but prévu de l'interface.

Par exemple, l'interface Runnable du package java.lang ; est une interface fonctionnelle car elle
ne contient qu'une seule méthode run().
Example 1: Definition d’un Functional Interface en java
import java.lang.FunctionalInterface;

@FunctionalInterface
public interface MyInterface{
// the single abstract method
double getValue();
}
Dans l'exemple ci-dessus, l'interface MyInterface n'a qu'une seule méthode abstraite getValue().
C'est donc une interface fonctionnelle.
Ici, nous avons utilisé l'annotation @FunctionalInterface. L'annotation force le compilateur Java
à indiquer que l'interface est une interface fonctionnelle. Par conséquent, ne permet pas d'avoir
plus d'une méthode abstraite. Cependant, ce n'est pas obligatoire pour autant.
En Java 7, les interfaces fonctionnelles étaient considérées comme de type Single Abstract
Method (SAM) et étaient couramment implémentés avec des classes anonymes.

Example 2: Implementation d’un SAM avec une classe anonyme


public class FunctionInterfaceTest {
public static void main(String[] args) {

// anonymous class
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I just implemented the
Runnable Functional Interface.");
}
}).start();
}
}
Exécution:
I just implemented the Runnable Functional Interface.
Dans cet exemple, nous avons passé une classe anonyme à une méthode (le constructeur
Runnable). Cela permet d'écrire des programmes avec moins de codes. Cependant, la syntaxe
était encore difficile et de nombreuses lignes de code supplémentaires étaient nécessaires.
Java 8 a étendu la puissance d'un SAM en allant encore plus loin. Puisque nous savons qu'une
interface fonctionnelle n'a qu'une seule méthode, il ne devrait pas être nécessaire de définir le
nom de cette méthode lors de la transmission en argument. L'expression lambda nous permet
de faire exactement cela.
Introduction au lambda expressions
L'expression lambda est essentiellement une méthode anonyme ou sans nom. L'expression
lambda ne s'exécute pas toute seule. Au lieu de cela, elle est utilisé pour implémenter une
méthode définie par une interface fonctionnelle.

Comment définir une Lambda Expression?

(parameter list) -> lambda body

Le nouvel opérateur (->) utilisé est appelé opérateur lambda.,


Exemples de lambda expressions

Considérons la fonction suivante :

double getPiValue() {
return 3.1415;
}
Nous pouvons écrire cette méthode en utilisant la Lambda expression ci-dessous :

() ->3.1415

Ici, la méthode getPiValue() n'a pas de paramètres. Par conséquent, le côté gauche de
l'opérateur Lambda comprend un paramètre vide. Le côté droit est le corps lambda qui spécifie
l'action de l'expression lambda. Dans ce cas, il renvoie la valeur 3,1415.
Types du corps de la Lambda Expression
Le corps d’une Lambda Expression peut être de deux types

1. Un corps formé d’une seule expression

() -> System.out.println("Lambdas are great");

Ce type de corps lambda est connu sous le nom de corps « expression »

2. Un corps formé d’un bloc de code.


() -> {
double pi = 3.1415;
return pi;
};
Ce type de corps lambda est connu sous le nom de corps « bloc ». Le corps bloc permet au corps
lambda d'inclure plusieurs instructions. Ces instructions sont placées entre les accolades et il
faut ajouter un point-virgule après les accolades.
Note: Pour le corps bloc, il est possible d’avoir une instruction return si le corps renvoie une
valeur. Cependant, le corps de l'expression ne nécessite pas d'instruction return

Exemple 3: Exemple de programme utilisant une Lambda Expression


Comme mentionné précédemment, une Lambda expression n'est pas exécutée seule. Au
contraire, il forme la mise en œuvre de la méthode abstraite définie par l'interface
fonctionnelle.

Nous devons donc d'abord définir une interface fonctionnelle.

import java.lang.FunctionalInterface;

// this is functional interface


@FunctionalInterface
interface MyInterface{
// abstract method
double getPiValue();
}
public class Main {
public static void main( String[] args ) {
// declare a reference to MyInterface
MyInterface ref;

// lambda expression
ref = () -> 3.1415;
System.out.println("Value of Pi = " + ref.getPiValue());
}
}

Dans l'exemple ci-dessus,


• Nous avons créé une interface fonctionnelle nommée MyInterface. Il contient une seule
méthode abstraite nommée getPiValue()
• Dans la classe Main, nous avons déclaré une référence à MyInterface. Notez que nous
pouvons déclarer une référence d'interface mais nous ne pouvons pas instancier une interface.
• Nous avons ensuite affecté une lambda expression à la référence.
• Enfin, nous appelons la méthode getPiValue() en utilisant la référence de l’interface.

Lambda Expressions avec paramètres


Jusqu'à présent, nous avons créé des lambda expressions sans aucun paramètre. Cependant, à
l'instar des méthodes, les lambda expressions peuvent également avoir des paramètres. Par
exemple,
(n) -> (n%2)==0
Dans cet exemple, la variable n entre parenthèses est un paramètre passé à l'expression
lambda. Le corps lambda prend le paramètre et vérifie s'il est pair ou impair.

Example 4: Utilisation d’une lambda expression avec paramètres


@FunctionalInterface
interface MyInterface {
// abstract method
String reverse(String n);
}
public class Main {
public static void main( String[] args ) {
// declare a reference to MyInterface
// assign a lambda expression to the reference
MyInterface ref = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
// call the method of the interface
System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
}
}
Interface Fonctionnel Generique
Jusqu'à présent, nous avons utilisé l'interface fonctionnelle qui n'accepte qu'un seul type de
valeur. Par exemple,
@FunctionalInterface
interface MyInterface {
String reverseString(String n);
}
L'interface fonctionnelle ci-dessus n'accepte qu’un argument de type String et renvoie une
valeur de type String. Cependant, nous pouvons rendre l'interface fonctionnelle générique, de
sorte que tout type de données soit accepté.

Example 5: Interface Fonctionnelle Generique et Lambda Expressions


@FunctionalInterface
interface GenericInterface<T> {
// generic method
T func(T t);
}
public class Main {
public static void main( String[] args ) {
// declare a reference to GenericInterface
// the GenericInterface operates on String data
// assign a lambda expression to it
GenericInterface<String> reverse = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
System.out.println("Lambda reversed = " + reverse.func("Lambda"));

// declare another reference to GenericInterface


// the GenericInterface operates on Integer data
// assign a lambda expression to it
GenericInterface<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1; i <= n; i++)
result = i * result;
return result;
};
System.out.println("factorial of 5 = " + factorial.func(5));
}
}
Dans l'exemple ci-dessus, nous avons créé une interface fonctionnelle générique nommée
GenericInterface. Il contient une méthode générique nommée func().
Dans la classe Main,
• GenericInterface<String> reverse - crée une référence à l'interface. L'interface fonctionne
désormais sur des données de type String.
• GenericInterface<Integer> factoriel - crée une référence à l'interface. L'interface, dans ce cas,
fonctionne sur le type de données Integer.

Lambda Expression et l’API Stream

Le nouveau package java.util.stream a été ajouté à JDK8, ce qui permet aux développeurs Java
d'effectuer des opérations telles que search, filter, map, reduce ou manipuler des collections
telles que des listes.
Par exemple, nous avons un flux de données (dans notre cas une liste de chaînes) où chaque
chaîne est une combinaison du nom du pays et du lieu du pays. Maintenant, nous pouvons
traiter ce flux de données et récupérer uniquement les lieux du Népal.
Pour cela, nous pouvons effectuer des opérations en masse dans le flux en combinant l'API
Stream et l'expression Lambda.

Example 6: Demonstration de l’utilisation des lambda expressions avec l’API Stream


import java.util.ArrayList;
import java.util.List;

public class StreamMain {


// create an object of list using ArrayList
static List<String> places = new ArrayList<>();

// preparing our data


public static List getPlaces(){
// add places and country to the list
places.add("Nepal, Kathmandu");
places.add("Nepal, Pokhara");
places.add("India, Delhi");
places.add("USA, New York");
places.add("Africa, Nigeria");
return places;
}

public static void main( String[] args ) {


List<String> myPlaces = getPlaces();
System.out.println("Places from Nepal:");
// Filter places from Nepal
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
}
}
Dans l'exemple ci-dessus, notez la déclaration,

// Filter places from Nepal


myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));

Ici, nous utilisons les méthodes telles que filter(), map() et forEach() de l'API Stream. Ces
méthodes peuvent prendre une lambda expression en entrée.

Vous aimerez peut-être aussi