Interface: istota, kontrakty w Javie i Kotlinie dla Androida

Autor: IT Sectr Opublikowano: 2026-02-18 Czas czytania: 9 min

Interface — to kontrakt określający zestaw abstrakcyjnych metod, które klasa musi zaimplementować. W Javie i Kotlinie interfejsy są głównym mechanizmem abstrakcji i polimorfizmu. W Javie 8+ interfejsy mogą zawierać metody default i static, w Kotlinie — implementacje domyślne. Według Google Android Developers (2025), interfejsy są używane w 90% projektów Android do definiowania warstw architektury — repozytoriów, UseCase i serwisów.

Najważniejsze

  • Interface — typ abstrakcyjny definiujący sygnatury metod bez implementacji (przed Java 8)
  • Implements — słowo kluczowe łączące klasę z interfejsem; klasa może implementować wiele interfejsów
  • Default method — metoda z implementacją w interfejsie Java, dodana w Javie 8 dla wstecznej kompatybilności
  • Kotlin interface obsługuje właściwości z getterami i implementacje metod, zastępując abstract class w wielu scenariuszach
  • Markup interface — pusty interfejs używany jako znacznik (Serializable, Cloneable, RandomAccess)

Czym jest Interface w Javie i Kotlinie?

Interface — to typ referencyjny, który zawiera abstrakcyjne metody, stałe i metody domyślne. Klasa implementująca interfejs musi dostarczyć implementację wszystkich jego abstrakcyjnych metod. W Javie interfejs nie może mieć stanu (pól instancji), Kotlin również przestrzega tego ograniczenia, ale obsługuje właściwości z akcesorami.

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;
    // gettery i settery
}

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();
    }
    // pozostałe metody
}

Repository<T> — interfejs generyczny z metodami CRUD. Metoda domyślna count() dostarcza domyślną implementację, którą można nadpisać. UserRepositoryImpl implementuje interfejs, używając EntityManager do dostępu do danych. Takie podejście pozwala testować warstwę danych przez mock interfejsu bez prawdziwej bazy danych.

Interface vs abstract class: kiedy co wybrać

Wybór między interfejsem a klasą abstrakcyjną zależy od obecności wspólnego stanu i relacji między typami. Interfejs określa kontrakt (co klasa może robić), klasa abstrakcyjna — wspólną implementację (czym klasa jest).

KryteriumInterfaceAbstract Class
Stan (pola)Tylko stałe static finalTak, dowolne pola
KonstruktoryNieTak
Dziedziczenie wielokrotneTak (implements)Nie (extends jeden)
Modyfikatory dostępupublic (Java 8-), metody defaultWszystkie (private, protected, public)
Kiedy używaćKontrakt dla niepowiązanych klasWspólna baza dla pokrewnych klas

W Clean Architecture interfejsy znajdują się w wewnętrznej warstwie (domain), a implementacje — w zewnętrznej (data). Pozwala to zachować regułę zależności: warstwa zewnętrzna zależy od wewnętrznej, ale nie odwrotnie. Klasy abstrakcyjne są częściej używane dla metod szablonowych (Template Method pattern).

Interface w Kotlinie: właściwości i delegaty

Interfejsy Kotlina są bardziej elastyczne niż w Javie: mogą deklarować abstrakcyjne właściwości i dostarczać implementacje metod. W przeciwieństwie do Javy, Kotlin obsługuje delegowanie przez słowo kluczowe by, co znacząco redukuje boilerplate przy implementacji wzorca Delegate.

Kotlin
interface ApiService {
    val baseUrl: String  // abstrakcyjna właściwość
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // domyślna implementacja
    }
}

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

// Delegacja przez 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 implementuje interfejs Logger przez delegowanie (by). Wszystkie wywołania log() są przekierowywane do obiektu logger bez pisania metod-opakowań. To przykład kompozycji, która w Javie wymagałaby 5-10 linii boilerplate-kodu.

Interface w Clean Architecture Android

Clean Architecture dla Androida wyraźnie dzieli aplikację na warstwy. Interfejsy pełnią rolę granic między warstwami: Domain definiuje interfejsy repozytoriów i UseCase, Data dostarcza implementacje. Pozwala to wymieniać implementacje bez zmiany logiki biznesowej — kluczowa zaleta przy migracji z Room na Firebase lub z REST na GraphQL.

Kotlin
// Warstwa Domain — interfejs (kontrakt)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Warstwa Domain — use case (zależy od interfejsu)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Warstwa Data — implementacja
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 — interfejs w warstwie domain. GetUserUseCase zależy od interfejsu, a nie od implementacji. UserRepositoryImpl w warstwie data implementuje interfejs, łącząc API i lokalną bazę danych. Taka architektura pozwala testować UseCase z mock-repozytorium bez konfiguracji bazy danych lub sieci.

Default i static metody w Javie 8+

Metody default zostały dodane w Javie 8 do ewolucyjnego rozszerzania interfejsów bez naruszania wstecznej kompatybilności. Gdyby ArrayList nie implementował nowej metody stream() dodanej do Collection, stary kod działałby dalej. Metody static w interfejsach służą jako narzędzia związane z interfejsem — alternatywa dla klas narzędziowych.

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();
    }
    
    // stała
    String CATEGORY = "transport";
}

Metody default rozwiązują problem diamentu (diamond problem): jeśli klasa implementuje dwa interfejsy z tą samą metodą default, kompilator wymaga jawnego nadpisania. Metody static są wywoływane przez nazwę interfejsu — Vehicle.of("car"), bez instancji.

Typowe błędy przy projektowaniu interfejsów

Błędy w projektowaniu interfejsów prowadzą do kruchego kodu, trudności w testowaniu i naruszeń SOLID. Rozważmy trzy częste problemy.

Interface Pollution — zbyt wiele metod w jednym interfejsie

Interfejs zawierający 15+ metod narusza Interface Segregation Principle (ISP). Przykład — stary java.util.Dictionary z 10+ metodami. Rozwiązanie: podzielić na kilka mniejszych interfejsów — ReadableRepository, WritableRepository, SearchableRepository. Klient (serwis) zależy tylko od potrzebnych mu metod.

Nadmiarowa abstrakcja — interfejs dla każdej klasy

Tworzenie interfejsu dla każdej klasy bez rzeczywistej potrzeby polimorfizmu to antywzorzec Interface overkill. Oznaka: interfejs ma dokładnie jedną implementację, a w projekcie nie ma planów dodawania alternatyw. Rozwiązanie: dodawać interfejs tylko gdy pojawia się druga wersja implementacji lub potrzeba mocka do testów.

Często zadawane pytania

Czym różni się interface od abstract class w Javie?

Interface określa tylko kontrakt (sygnatury metod), nie może mieć stanu i obsługuje dziedziczenie wielokrotne. Abstract class może zawierać pola, konstruktory i zaimplementowane metody, ale klasa może dziedziczyć tylko jedną klasę abstrakcyjną. Od Javy 8 interfejsy otrzymały metody default i static, zmniejszając różnicę.

Czy interfejs może dziedziczyć inny interfejs?

Tak, w Javie i Kotlinie interfejsy obsługują dziedziczenie. public interface AdvancedRepository<T> extends Repository<T>, Pageable — interfejs łączący dwa inne. Klasa implementująca AdvancedRepository musi zaimplementować wszystkie metody obu rodzicielskich interfejsów. Dziedziczenie wielokrotne jest dozwolone tylko dla interfejsów.

Czym jest interfejs funkcyjny w Javie?

Interfejs funkcyjny — interfejs z jedną metodą abstrakcyjną (SAM — Single Abstract Method). Adnotacja @FunctionalInterface gwarantuje to ograniczenie. Przykłady: Runnable, Callable, Comparator, Consumer. Interfejsy funkcyjne są podstawą wyrażeń lambda w Javie 8: () -> System.out.println() implementuje Runnable.

Po co są metody default w interfejsach Javy?

Metody default pozwalają dodawać nowe metody do interfejsu bez zmiany wszystkich implementujących klas. Na przykład Java 8 dodała stream() do Collection jako metodę default. Bez tego mechanizmu foreach(), stream() i inne metody wymagałyby zmiany tysięcy klas w JDK. Default — to wstecznie kompatybilny sposób rozszerzania.

Jak Kotlin rozwiązuje problem dziedziczenia wielokrotnego?

Kotlin zabrania wielokrotnego dziedziczenia klas, ale zezwala na wielokrotną implementację interfejsów. Jeśli dwa interfejsy mają metodę o tej samej sygnaturze i domyślnej implementacji, kompilator wymaga jawnego nadpisania z wywołaniem super<InterfaceName>.method(). Rozwiązuje to problem diamentu na poziomie kompilacji.

Podsumowanie

  • Interface — kontrakt określający metody do implementacji; główny mechanizm polimorfizmu w Javie i Kotlinie
  • Java 8+ dodała metody default i static w interfejsach, zmniejszając różnicę z klasami abstrakcyjnymi
  • Kotlin interfaces obsługują abstrakcyjne właściwości, domyślne implementacje i delegowanie przez by
  • Clean Architecture używa interfejsów jako granic między warstwami Domain i Data
  • Interface Segregation Principle wymaga dzielenia dużych interfejsów na wyspecjalizowane
  • Interfejsy funkcyjne (Single Abstract Method) — podstawa wyrażeń lambda i Stream API
  • Zalecenie: dodawaj interfejs przy pojawieniu się drugiej implementacji lub potrzebie mocka do testowania

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również