Interface : essence, contrats en Java et Kotlin pour Android

Auteur : IT Sectr Publié le : 2026-02-18 Temps de lecture : 9 min

Interface est un contrat définissant un ensemble de méthodes abstraites qu'une classe doit implémenter. En Java et Kotlin, les interfaces sont le mécanisme principal d'abstraction et de polymorphisme. En Java 8+, les interfaces peuvent contenir des méthodes default et static, en Kotlin — des implémentations par défaut. Selon Google Android Developers (2025), les interfaces sont utilisées dans 90% des projets Android pour définir les couches d'architecture — référentiels, UseCases et services.

Points clés

  • Interface est un type abstrait définissant des signatures de méthodes sans implémentation (avant Java 8)
  • Implements est le mot-clé liant une classe à une interface ; une classe peut implémenter plusieurs interfaces
  • Default method est une méthode avec implémentation dans une interface Java, ajoutée en Java 8 pour la rétrocompatibilité
  • Kotlin interface prend en charge les propriétés avec getters et les implémentations de méthodes, remplaçant abstract class dans de nombreux scénarios
  • Markup interface est une interface vide utilisée comme marqueur (Serializable, Cloneable, RandomAccess)

Qu'est-ce que Interface en Java et Kotlin ?

Interface est un type de référence qui contient des méthodes abstraites, des constantes et des méthodes par défaut. Une classe implémentant une interface doit fournir une implémentation pour toutes ses méthodes abstraites. En Java, une interface ne peut pas avoir d'état (champs d'instance) ; Kotlin suit également cette restriction mais prend en charge les propriétés avec accesseurs.

Java
public interface Repository {
    T findById(Long id);
    List findAll();
    T save(T entity);
    void deleteById(Long id);
    
    default long count() {
        return findAll().size();
    }
}

@Entity
public class UserEntity {
    private Long id;
    private String email;
    // getters et setters
}

public class UserRepositoryImpl implements Repository {
    private final EntityManager em;
    
    public UserEntity findById(Long id) {
        return em.find(UserEntity.class, id);
    }
    
    public List findAll() {
        return em.createQuery("FROM UserEntity", UserEntity.class)
                .getResultList();
    }
    // autres méthodes
}

Repository<T> est une interface générique avec des méthodes CRUD. La méthode default count() fournit une implémentation par défaut qui peut être redéfinie. UserRepositoryImpl implémente l'interface en utilisant EntityManager pour l'accès aux données. Cette approche permet de tester la couche de données via un mock de l'interface sans base de données réelle.

Interface vs abstract class : quand choisir quoi

Le choix entre une interface et une classe abstraite dépend de la présence d'un état partagé et des relations entre types. Une interface définit un contrat (ce qu'une classe peut faire), une classe abstraite définit une implémentation commune (ce qu'une classe est).

CritèreInterfaceAbstract Class
État (champs)Uniquement des constantes static finalOui, tous champs
ConstructeursNonOui
Héritage multipleOui (implements)Non (extends un)
Modificateurs d'accèspublic (Java 8-), méthodes defaultTous (private, protected, public)
Quand utiliserContrat pour classes non liéesBase commune pour classes liées

Dans Clean Architecture, les interfaces sont placées dans la couche interne (domain) et les implémentations dans la couche externe (data). Cela permet de maintenir la Dependency Rule : la couche externe dépend de la couche interne, mais pas l'inverse. Les classes abstraites sont plus souvent utilisées pour les méthodes template (Template Method pattern).

Interface en Kotlin : propriétés et délégués

Les interfaces Kotlin sont plus flexibles que Java : elles peuvent déclarer des propriétés abstraites et fournir des implémentations de méthodes. Contrairement à Java, Kotlin prend en charge la délégation via le mot-clé by, ce qui réduit considérablement le boilerplate lors de l'implémentation du pattern Delegate.

Kotlin
interface ApiService {
    val baseUrl: String  // propriété abstraite
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // implémentation par défaut
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // implémentation de la requête
    }
}

// Délégation via by
interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println(message)
}

class UserService(logger: Logger) : Logger by logger

UserService implémente l'interface Logger par délégation (by). Tous les appels log() sont redirigés vers l'objet logger sans écrire de méthodes wrapper. C'est un exemple de composition qui nécessiterait 5 à 10 lignes de code boilerplate en Java.

Interface dans Clean Architecture Android

Clean Architecture pour Android sépare clairement l'application en couches. Les interfaces agissent comme des frontières entre les couches : Domain définit les interfaces des référentiels et UseCases, Data fournit les implémentations. Cela permet de remplacer les implémentations sans modifier la logique métier — un avantage clé lors de la migration de Room vers Firebase ou de REST vers GraphQL.

Kotlin
// Domain layer — interface (contrat)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain layer — use case (dépend de l'interface)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data layer — implémentation
class UserRepositoryImpl(
    private val api: UserApi,
    private val dao: UserDao
) : UserRepository {
    override suspend fun getUser(id: String): User {
        val cached = dao.getUser(id)
        if (cached != null) return cached
        val remote = api.fetchUser(id)
        dao.insertUser(remote)
        return remote
    }
    // updateUser...
}

UserRepository est une interface dans la couche domain. GetUserUseCase dépend de l'interface, pas de l'implémentation. UserRepositoryImpl dans la couche data implémente l'interface, combinant API et base de données locale. Cette architecture permet de tester les UseCases avec un référentiel mock sans configurer de base de données ou de réseau.

Méthodes default et static en Java 8+

Les méthodes default ont été ajoutées en Java 8 pour l'extension évolutive des interfaces sans casser la rétrocompatibilité. Si ArrayList n'implémentait pas la nouvelle méthode stream() ajoutée à Collection, l'ancien code continuerait de fonctionner. Les méthodes static dans les interfaces servent d'utilitaires liés à l'interface — une alternative aux classes utilitaires.

Java
public interface Vehicle {
    void start();
    void stop();
    
    default void honk() {
        System.out.println("Beep!");
    }
    
    static Vehicle of(String type) {
        if ("car".equals(type)) return new Car();
        return new Bicycle();
    }
    
    // constante
    String CATEGORY = "transport";
}

Les méthodes default résolvent le diamond problem : si une classe implémente deux interfaces avec la même méthode default, le compilateur exige une redéfinition explicite. Les méthodes static sont appelées via le nom de l'interface — Vehicle.of("car"), sans instance.

Erreurs courantes dans la conception d'interfaces

Les erreurs dans la conception d'interfaces conduisent à du code fragile, une complexité de test et des violations SOLID. Examinons trois problèmes courants.

Interface Pollution — trop de méthodes dans une interface

Une interface contenant 15+ méthodes viole le Interface Segregation Principle (ISP). Un exemple est l'ancien java.util.Dictionary avec 10+ méthodes. Solution : diviser en plusieurs petites interfaces — ReadableRepository, WritableRepository, SearchableRepository. Un client (service) dépend uniquement des méthodes dont il a besoin.

Abstraction excessive — interface pour chaque classe

Créer une interface pour chaque classe sans réel besoin de polymorphisme est l'anti-patron Interface overkill. Indicateur : l'interface a exactement une implémentation et le projet n'a pas de plans pour ajouter des alternatives. Solution : ajouter une interface seulement lorsqu'une deuxième option d'implémentation apparaît ou qu'un mock est nécessaire pour les tests.

Questions fréquentes

En quoi interface diffère-t-il de abstract class en Java ?

Interface définit uniquement un contrat (signatures de méthodes), ne peut pas avoir d'état et prend en charge l'héritage multiple. Abstract class peut contenir des champs, des constructeurs et des méthodes implémentées, mais une classe ne peut hériter que d'une seule classe abstraite. Depuis Java 8, les interfaces ont obtenu des méthodes default et static, réduisant l'écart.

Une interface peut-elle hériter d'une autre interface ?

Oui, en Java et Kotlin, les interfaces prennent en charge l'héritage. public interface AdvancedRepository<T> extends Repository<T>, Pageable est une interface combinant deux autres. Une classe implémentant AdvancedRepository doit implémenter toutes les méthodes des deux interfaces parentes. L'héritage multiple n'est autorisé que pour les interfaces.

Qu'est-ce qu'une interface fonctionnelle en Java ?

Interface fonctionnelle est une interface avec une seule méthode abstraite (SAM — Single Abstract Method). L'annotation @FunctionalInterface garantit cette restriction. Exemples : Runnable, Callable, Comparator, Consumer. Les interfaces fonctionnelles sont la base des expressions lambda Java 8 : () -> System.out.println() implémente Runnable.

Pourquoi les méthodes default sont-elles nécessaires dans les interfaces Java ?

Les méthodes default permettent d'ajouter de nouvelles méthodes à une interface sans modifier toutes les classes qui l'implémentent. Par exemple, Java 8 a ajouté stream() à Collection comme méthode default. Sans ce mécanisme, foreach(), stream() et d'autres méthodes auraient nécessité des modifications dans des milliers de classes du JDK. Default est une méthode d'extension rétrocompatible.

Comment Kotlin résout-il le problème d'héritage multiple ?

Kotlin interdit l'héritage multiple de classes mais permet l'implémentation multiple d'interfaces. Si deux interfaces ont une méthode avec la même signature et implémentation par défaut, le compilateur exige une redéfinition explicite avec l'appel super<InterfaceName>.method(). Cela résout le diamond problem au niveau de la compilation.

Résumé

  • Interface est un contrat définissant les méthodes qu'une classe doit implémenter ; le mécanisme principal de polymorphisme en Java et Kotlin
  • Java 8+ a ajouté des méthodes default et static aux interfaces, réduisant l'écart avec les classes abstraites
  • Kotlin interfaces prennent en charge les propriétés abstraites, les implémentations par défaut et la délégation via by
  • Clean Architecture utilise les interfaces comme frontières entre les couches Domain et Data
  • Interface Segregation Principle exige de diviser les grandes interfaces en interfaces spécialisées
  • Interfaces fonctionnelles (Single Abstract Method) sont la base des expressions lambda et de Stream API
  • Recommandation : ajoutez une interface lorsqu'une deuxième implémentation apparaît ou qu'un mock est nécessaire pour les tests

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi