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, что мощно сокращает boilerplate при реализации паттерна 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>
{
// реализация запроса
}
}
// Delegation через 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 строк boilerplate-кода.
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 methods были добавлены в 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также