Interface: essentie, contracten in Java en Kotlin voor Android

Auteur: IT Sectr Gepubliceerd: 2026-02-18 Leestijd: 9 min

Interface — is een contract dat een reeks abstracte methoden definieert die een klasse moet implementeren. In Java en Kotlin zijn interfaces het belangrijkste mechanisme voor abstractie en polymorfisme. In Java 8+ kunnen interfaces default en static methoden bevatten, in Kotlin — standaardimplementaties. Volgens Google Android Developers (2025) worden interfaces in 90% van de Android-projecten gebruikt om architectuurlagen te definiëren — repositories, UseCases en services.

Belangrijkste

  • Interface — abstract type dat methodesignaturen definieert zonder implementatie (vóór Java 8)
  • Implements — sleutelwoord dat een klasse aan een interface koppelt; een klasse kan meerdere interfaces implementeren
  • Default method — methode met implementatie in een Java-interface, toegevoegd in Java 8 voor achterwaartse compatibiliteit
  • Kotlin interface ondersteunt eigenschappen met getters en methodimplementaties, en vervangt abstract class in veel scenario's
  • Markup interface — lege interface gebruikt als markering (Serializable, Cloneable, RandomAccess)

Wat is Interface in Java en Kotlin?

Interface — is een referentietype dat abstracte methoden, constanten en standaardmethoden bevat. Een klasse die een interface implementeert, moet de implementatie van al zijn abstracte methoden leveren. In Java kan een interface geen toestand hebben (instantievelden), Kotlin volgt deze beperking ook, maar ondersteunt eigenschappen met accessors.

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 en 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();
    }
    // overige methoden
}

Repository<T> — een generieke interface met CRUD-methoden. De default-methode count() biedt een standaardimplementatie die kan worden overschreven. UserRepositoryImpl implementeert de interface met EntityManager voor gegevenstoegang. Deze aanpak maakt het mogelijk om de gegevenslaag te testen via een mock van de interface zonder echte database.

Interface vs abstract class: wanneer wat te kiezen

De keuze tussen een interface en een abstracte klasse hangt af van de aanwezigheid van gedeelde toestand en relaties tussen typen. Een interface definieert een contract (wat een klasse kan doen), een abstracte klasse — een gemeenschappelijke implementatie (wat een klasse is).

CriteriumInterfaceAbstract Class
Toestand (velden)Alleen static final constantenJa, alle velden
ConstructorsNeeJa
Meervoudige overervingJa (implements)Nee (extends één)
Toegangsmodifierspublic (Java 8-), default methodenAlle (private, protected, public)
Wanneer gebruikenContract voor niet-verwante klassenGemeenschappelijke basis voor verwante klassen

In Clean Architecture bevinden interfaces zich in de binnenste laag (domain) en implementaties in de buitenste laag (data). Dit maakt naleving van de Dependency Rule mogelijk: de buitenste laag is afhankelijk van de binnenste, niet andersom. Abstracte klassen worden vaker gebruikt voor sjabloonmethoden (Template Method pattern).

Interface in Kotlin: eigenschappen en delegaten

Kotlin-interfaces zijn flexibeler dan Java: ze kunnen abstracte eigenschappen declareren en methodimplementaties bieden. In tegenstelling tot Java ondersteunt Kotlin delegatie via het sleutelwoord by, wat boilerplate aanzienlijk vermindert bij het implementeren van het Delegate-patroon.

Kotlin
interface ApiService {
    val baseUrl: String  // abstracte eigenschap
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // standaardimplementatie
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // implementatie van de aanvraag
    }
}

// Delegatie 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 implementeert de Logger-interface via delegatie (by). Alle log()-aanroepen worden doorgestuurd naar het logger-object zonder wrapper-methoden te schrijven. Dit is een voorbeeld van compositie die in Java 5-10 regels boilerplate-code zou vereisen.

Interface in Clean Architecture Android

Clean Architecture voor Android verdeelt de applicatie duidelijk in lagen. Interfaces fungeren als grenzen tussen lagen: Domain definieert interfaces voor repositories en UseCases, Data levert implementaties. Dit maakt het mogelijk om implementaties te vervangen zonder de bedrijfslogica te wijzigen — een belangrijk voordeel bij migratie van Room naar Firebase of van REST naar GraphQL.

Kotlin
// Domain-laag — interface (contract)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain-laag — use case (afhankelijk van interface)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data-laag — implementatie
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 — interface in de domain-laag. GetUserUseCase is afhankelijk van de interface, niet van de implementatie. UserRepositoryImpl in de data-laag implementeert de interface en combineert API en lokale database. Deze architectuur maakt het mogelijk om UseCases te testen met een mock-repository zonder database- of netwerkconfiguratie.

Default en static methoden in Java 8+

Default methoden zijn toegevoegd in Java 8 voor evolutionaire uitbreiding van interfaces zonder achterwaartse compatibiliteit te schenden. Als ArrayList de nieuwe stream()-methode die aan Collection was toegevoegd niet had geïmplementeerd, zou oude code blijven werken. Static methoden in interfaces dienen voor hulpprogramma's die bij de interface horen — een alternatief voor utility-klassen.

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";
}

Default-methoden lossen het diamond problem op: als een klasse twee interfaces met dezelfde default-methode implementeert, vereist de compiler expliciete overschrijving. Static-methoden worden aangeroepen via de interfacenaam — Vehicle.of("car"), zonder instantie.

Veelgemaakte fouten bij het ontwerpen van interfaces

Fouten bij het ontwerpen van interfaces leiden tot breekbare code, testproblemen en SOLID-schendingen. Laten we drie veelvoorkomende problemen bekijken.

Interface Pollution — te veel methoden in één interface

Een interface met 15+ methoden schendt het Interface Segregation Principle (ISP). Voorbeeld — de oude java.util.Dictionary met 10+ methoden. Oplossing: splitsen in meerdere kleine interfaces — ReadableRepository, WritableRepository, SearchableRepository. De klant (service) is alleen afhankelijk van de methoden die hij nodig heeft.

Overmatige abstractie — interface voor elke klasse

Het maken van een interface voor elke klasse zonder echte behoefte aan polymorfisme is het antipatroon Interface overkill. Kenmerk: de interface heeft precies één implementatie en er zijn geen plannen om alternatieven toe te voegen. Oplossing: voeg een interface alleen toe wanneer er een tweede implementatievariant ontstaat of behoefte is aan een mock voor tests.

Veelgestelde vragen

Wat is het verschil tussen interface en abstract class in Java?

Interface definieert alleen het contract (methodesignaturen), kan geen toestand hebben en ondersteunt meervoudige overerving. Abstract class kan velden, constructors en geïmplementeerde methoden bevatten, maar een klasse kan slechts één abstracte klasse erven. Sinds Java 8 hebben interfaces default en static methoden gekregen, waardoor het verschil kleiner is geworden.

Kan een interface een andere interface erven?

Ja, in Java en Kotlin ondersteunen interfaces overerving. public interface AdvancedRepository<T> extends Repository<T>, Pageable — een interface die twee andere combineert. Een klasse die AdvancedRepository implementeert, moet alle methoden van beide ouderinterfaces implementeren. Meervoudige overerving is alleen toegestaan voor interfaces.

Wat is een functionele interface in Java?

Functionele interface — een interface met één abstracte methode (SAM — Single Abstract Method). De annotatie @FunctionalInterface garandeert deze beperking. Voorbeelden: Runnable, Callable, Comparator, Consumer. Functionele interfaces vormen de basis van lambda-expressies in Java 8: () -> System.out.println() implementeert Runnable.

Waarom zijn default methoden nodig in Java-interfaces?

Default methoden maken het mogelijk om nieuwe methoden aan een interface toe te voegen zonder alle implementerende klassen te wijzigen. Java 8 voegde bijvoorbeeld stream() toe aan Collection als default-methode. Zonder dit mechanisme zouden foreach(), stream() en andere methoden duizenden klassen in de JDK hebben moeten wijzigen. Default — een achterwaarts compatibele manier van uitbreiden.

Hoe lost Kotlin het probleem van meervoudige overerving op?

Kotlin verbiedt meervoudige overerving van klassen, maar staat meervoudige implementatie van interfaces toe. Als twee interfaces een methode met dezelfde signatuur en standaardimplementatie hebben, vereist de compiler expliciete overschrijving met een aanroep van super<InterfaceName>.method(). Dit lost het diamond problem op compilatieniveau op.

Samenvatting

  • Interface — contract dat te implementeren methoden definieert; het belangrijkste polymorfisme-mechanisme in Java en Kotlin
  • Java 8+ voegde default en static methoden toe aan interfaces, waardoor het verschil met abstracte klassen kleiner werd
  • Kotlin interfaces ondersteunen abstracte eigenschappen, standaardimplementaties en delegatie via by
  • Clean Architecture gebruikt interfaces als grenzen tussen Domain- en Data-lagen
  • Interface Segregation Principle vereist het opsplitsen van grote interfaces in gespecialiseerde interfaces
  • Functionele interfaces (Single Abstract Method) — basis van lambda-expressies en Stream API
  • Aanbeveling: voeg een interface toe wanneer een tweede implementatie of behoefte aan een mock voor testen ontstaat

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook