Interface — це контракт, що визначає набір абстрактних методів, які має реалізувати клас. У Java та Kotlin інтерфейси — основний механізм абстракції та поліморфізму. У Java 8+ інтерфейси можуть містити default та static методи, у Kotlin — реалізації за замовчуванням. За даними Google Android Developers (2025), інтерфейси використовуються в 90% Android-проєктів для визначення шарів архітектури — репозиторіїв, UseCase та сервісів.
Головне
Interface — це посилальний тип, що містить абстрактні методи, константи та методи за замовчуванням. Клас, що реалізує інтерфейс, зобов'язаний надати реалізацію всіх його абстрактних методів. У Java інтерфейс не може мати стан (поля екземпляра), Kotlin також дотримується цього обмеження, але підтримує властивості з аксесорами.
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 | Abstract Class |
|---|---|---|
| Стан (поля) | Тільки static final константи | Так, будь-які поля |
| Конструктори | Ні | Так |
| Множинне наслідування | Так (implements) | Ні (extends один) |
| Модифікатори доступу | public (Java 8-), default методи | Всі (private, protected, public) |
| Коли використовувати | Контракт для незв'язаних класів | Спільна база для споріднених класів |
У Clean Architecture інтерфейси розміщуються у внутрішньому шарі (domain), а реалізації — у зовнішньому (data). Це дозволяє дотримуватися Dependency Rule: зовнішній шар залежить від внутрішнього, але не навпаки. Абстрактні класи частіше використовуються для шаблонних методів (Template Method pattern).
Інтерфейси Kotlin більш гнучкі, ніж Java: вони можуть оголошувати абстрактні властивості та надавати реалізації методів. На відміну від Java, Kotlin підтримує делегування через ключове слово by, що потужно скорочує шаблонний код при реалізації патерну Delegate.
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 loggerUserService реалізує інтерфейс Logger через делегування (by). Всі виклики log() перенаправляються об'єкту logger без написання методів-обгорток. Це приклад композиції, яка в Java потребувала б 5-10 рядків шаблонного коду.
Clean Architecture для Android чітко розділяє застосунок на шари. Інтерфейси відіграють роль меж між шарами: Domain визначає інтерфейси репозиторіїв та UseCase, Data надає реалізації. Це дозволяє замінювати реалізації без зміни бізнес-логіки — ключова перевага при міграції з Room на Firebase або з REST на GraphQL.
// 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 методи були додані в Java 8 для еволюційного розширення інтерфейсів без порушення зворотної сумісності. Якби ArrayList не реалізовував новий метод stream(), доданий до Collection, старий код продовжив би працювати. Static методи в інтерфейсах служать для утиліт, що належать до інтерфейсу, — альтернатива класам-утилітам.
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. Розглянемо три поширені проблеми.
Інтерфейс, що містить 15+ методів, порушує Interface Segregation Principle (ISP). Приклад — старий java.util.Dictionary з 10+ методами. Рішення: розділити на кілька дрібних інтерфейсів — ReadableRepository, WritableRepository, SearchableRepository. Клієнт (сервіс) залежить тільки від потрібних йому методів.
Створення інтерфейсу для кожного класу без реальної потреби в поліморфізмі — антипатерн Interface overkill. Ознака: в інтерфейсу рівно одна реалізація, і в проєкті немає планів щодо додавання альтернатив. Рішення: додавати інтерфейс тільки коли з'являється другий варіант реалізації або потреба в mock для тестів.
Часті запитання
Interface визначає тільки контракт (сигнатури методів), не може мати стану та підтримує множинне наслідування. Abstract class може містити поля, конструктори та реалізовані методи, але клас може успадкувати лише один абстрактний клас. З Java 8 інтерфейси отримали default та static методи, скоротивши розрив.
Так, у Java та Kotlin інтерфейси підтримують наслідування. public interface AdvancedRepository<T> extends Repository<T>, Pageable — інтерфейс, що об'єднує два інших. Клас, що реалізує AdvancedRepository, повинен реалізувати всі методи обох батьківських інтерфейсів. Множинне наслідування дозволене тільки для інтерфейсів.
Функціональний інтерфейс — інтерфейс з одним абстрактним методом (SAM — Single Abstract Method). Анотація @FunctionalInterface гарантує це обмеження. Приклади: Runnable, Callable, Comparator, Consumer. Функціональні інтерфейси — основа лямбда-виразів Java 8: () -> System.out.println() реалізує Runnable.
Default методи дозволяють додавати нові методи в інтерфейс без зміни всіх реалізуючих класів. Наприклад, Java 8 додала stream() у Collection як default-метод. Без цього механізму foreach(), stream() та інші методи потребували б змін тисяч класів у JDK. Default — зворотно сумісний спосіб розширення.
Kotlin забороняє множинне наслідування класів, але дозволяє множинну реалізацію інтерфейсів. Якщо два інтерфейси мають метод з однаковою сигнатурою та реалізацією за замовчуванням, компілятор вимагає явного перевизначення з викликом super<InterfaceName>.method(). Це вирішує diamond problem на рівні компіляції.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також