Interface: суть, контракти в Java та Kotlin для Android

Автор: IT Sectr Опубліковано: 2026-02-18 Час читання: 9 хв

Interface — це контракт, що визначає набір абстрактних методів, які має реалізувати клас. У Java та Kotlin інтерфейси — основний механізм абстракції та поліморфізму. У Java 8+ інтерфейси можуть містити default та static методи, у Kotlin — реалізації за замовчуванням. За даними Google Android Developers (2025), інтерфейси використовуються в 90% Android-проєктів для визначення шарів архітектури — репозиторіїв, UseCase та сервісів.

Головне

  • Interface — абстрактний тип, що визначає сигнатури методів без реалізації (до Java 8)
  • Implements — ключове слово, що пов'язує клас з інтерфейсом; клас може реалізувати кілька інтерфейсів
  • Default method — метод з реалізацією в інтерфейсі Java, доданий у Java 8 для зворотної сумісності
  • Kotlin interface підтримує властивості з геттерами та реалізації методів, замінюючи abstract class у багатьох сценаріях
  • Markup interface — порожній інтерфейс, який використовується як мітка (Serializable, Cloneable, RandomAccess)

Що таке Interface у Java та Kotlin?

Interface — це посилальний тип, що містить абстрактні методи, константи та методи за замовчуванням. Клас, що реалізує інтерфейс, зобов'язаний надати реалізацію всіх його абстрактних методів. У Java інтерфейс не може мати стан (поля екземпляра), Kotlin також дотримується цього обмеження, але підтримує властивості з аксесорами.

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;
    // геттери та сеттери
}

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();
    }
    // інші методи
}

Repository<T> — generic-інтерфейс з методами CRUD. Default-метод count() надає реалізацію за замовчуванням, яку можна перевизначити. UserRepositoryImpl реалізує інтерфейс, використовуючи EntityManager для доступу до даних. Такий підхід дозволяє тестувати шар даних через mock інтерфейсу без реальної БД.

Interface vs abstract class: коли що вибрати

Вибір між інтерфейсом та абстрактним класом залежить від наявності спільного стану та відносин між типами. Інтерфейс визначає контракт (що клас може робити), абстрактний клас — спільну реалізацію (чим клас є).

КритерійInterfaceAbstract Class
Стан (поля)Тільки static final константиТак, будь-які поля
КонструкториНіТак
Множинне наслідуванняТак (implements)Ні (extends один)
Модифікатори доступуpublic (Java 8-), default методиВсі (private, protected, public)
Коли використовуватиКонтракт для незв'язаних класівСпільна база для споріднених класів

У Clean Architecture інтерфейси розміщуються у внутрішньому шарі (domain), а реалізації — у зовнішньому (data). Це дозволяє дотримуватися Dependency Rule: зовнішній шар залежить від внутрішнього, але не навпаки. Абстрактні класи частіше використовуються для шаблонних методів (Template Method pattern).

Interface у Kotlin: властивості та делегати

Інтерфейси Kotlin більш гнучкі, ніж Java: вони можуть оголошувати абстрактні властивості та надавати реалізації методів. На відміну від Java, Kotlin підтримує делегування через ключове слово by, що потужно скорочує шаблонний код при реалізації патерну Delegate.

Kotlin
interface ApiService {
    val baseUrl: String  // абстрактна властивість
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // реалізація за замовчуванням
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // реалізація запиту
    }
}

// Делегування через 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 реалізує інтерфейс Logger через делегування (by). Всі виклики log() перенаправляються об'єкту logger без написання методів-обгорток. Це приклад композиції, яка в Java потребувала б 5-10 рядків шаблонного коду.

Interface у Clean Architecture Android

Clean Architecture для Android чітко розділяє застосунок на шари. Інтерфейси відіграють роль меж між шарами: Domain визначає інтерфейси репозиторіїв та UseCase, Data надає реалізації. Це дозволяє замінювати реалізації без зміни бізнес-логіки — ключова перевага при міграції з Room на Firebase або з REST на GraphQL.

Kotlin
// Domain layer — інтерфейс (контракт)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain layer — use case (залежить від інтерфейсу)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data layer — реалізація
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 — інтерфейс у domain-шарі. GetUserUseCase залежить від інтерфейсу, а не від реалізації. UserRepositoryImpl у data-шарі реалізує інтерфейс, комбінуючи API та локальну БД. Така архітектура дозволяє тестувати UseCase з mock-репозиторієм без налаштування бази даних або мережі.

Default та static методи в Java 8+

Default методи були додані в Java 8 для еволюційного розширення інтерфейсів без порушення зворотної сумісності. Якби ArrayList не реалізовував новий метод stream(), доданий до Collection, старий код продовжив би працювати. Static методи в інтерфейсах служать для утиліт, що належать до інтерфейсу, — альтернатива класам-утилітам.

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();
    }
    
    // константа
    String CATEGORY = "transport";
}

Default-методи вирішують diamond problem: якщо клас реалізує два інтерфейси з однаковим default-методом, компілятор вимагає явного перевизначення. Static-методи викликаються через ім'я інтерфейсу — Vehicle.of("car"), без екземпляра.

Типові помилки при проєктуванні інтерфейсів

Помилки в проєктуванні інтерфейсів призводять до крихкого коду, складності тестування та порушень SOLID. Розглянемо три поширені проблеми.

Interface Pollution — забагато методів в одному інтерфейсі

Інтерфейс, що містить 15+ методів, порушує Interface Segregation Principle (ISP). Приклад — старий java.util.Dictionary з 10+ методами. Рішення: розділити на кілька дрібних інтерфейсів — ReadableRepository, WritableRepository, SearchableRepository. Клієнт (сервіс) залежить тільки від потрібних йому методів.

Надмірна абстракція — інтерфейс під кожен клас

Створення інтерфейсу для кожного класу без реальної потреби в поліморфізмі — антипатерн Interface overkill. Ознака: в інтерфейсу рівно одна реалізація, і в проєкті немає планів щодо додавання альтернатив. Рішення: додавати інтерфейс тільки коли з'являється другий варіант реалізації або потреба в mock для тестів.

Часті запитання

Чим interface відрізняється від abstract class у Java?

Interface визначає тільки контракт (сигнатури методів), не може мати стану та підтримує множинне наслідування. Abstract class може містити поля, конструктори та реалізовані методи, але клас може успадкувати лише один абстрактний клас. З Java 8 інтерфейси отримали default та static методи, скоротивши розрив.

Чи може інтерфейс успадковувати інший інтерфейс?

Так, у Java та Kotlin інтерфейси підтримують наслідування. public interface AdvancedRepository<T> extends Repository<T>, Pageable — інтерфейс, що об'єднує два інших. Клас, що реалізує AdvancedRepository, повинен реалізувати всі методи обох батьківських інтерфейсів. Множинне наслідування дозволене тільки для інтерфейсів.

Що таке функціональний інтерфейс у Java?

Функціональний інтерфейс — інтерфейс з одним абстрактним методом (SAM — Single Abstract Method). Анотація @FunctionalInterface гарантує це обмеження. Приклади: Runnable, Callable, Comparator, Consumer. Функціональні інтерфейси — основа лямбда-виразів Java 8: () -> System.out.println() реалізує Runnable.

Навіщо потрібні default методи в інтерфейсах Java?

Default методи дозволяють додавати нові методи в інтерфейс без зміни всіх реалізуючих класів. Наприклад, Java 8 додала stream() у Collection як default-метод. Без цього механізму foreach(), stream() та інші методи потребували б змін тисяч класів у JDK. Default — зворотно сумісний спосіб розширення.

Як Kotlin вирішує проблему множинного наслідування?

Kotlin забороняє множинне наслідування класів, але дозволяє множинну реалізацію інтерфейсів. Якщо два інтерфейси мають метод з однаковою сигнатурою та реалізацією за замовчуванням, компілятор вимагає явного перевизначення з викликом super<InterfaceName>.method(). Це вирішує diamond problem на рівні компіляції.

Підсумки

  • Interface — контракт, що визначає методи, які має реалізувати клас; основний механізм поліморфізму в Java та Kotlin
  • Java 8+ додала default та static методи в інтерфейси, скоротивши розрив з абстрактними класами
  • Kotlin interfaces підтримують абстрактні властивості, реалізації за замовчуванням та делегування через by
  • Clean Architecture використовує інтерфейси як межі між шарами Domain та Data
  • Interface Segregation Principle вимагає розділення великих інтерфейсів на спеціалізовані
  • Функціональні інтерфейси (Single Abstract Method) — основа лямбда-виразів та Stream API
  • Рекомендація: додавайте інтерфейс при появі другої реалізації або потреби в mock для тестування

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також