--- title: Интерфейсы description: Реализация по умолчанию, свойства без состояния, ромбовидное наследование и fun interface tags: - kotlin - oop --- Интерфейс описывает контракт. В Kotlin он может содержать и реализацию. ```kotlin interface Drawable2 { fun draw(): String // только объявление fun show() = "рисую: ${draw()}" // реализация по умолчанию } class Circle4 : Drawable2 { override fun draw() = "круг" } fun main() { println(Circle4().show()) // рисую: круг } ``` Скобок после имени интерфейса в списке супертипов нет - конструктора у него не бывает. ## Свойства в интерфейсе Свойство объявить можно, но **хранить значение - нет**: ```kotlin interface Named2 { val name: String // абстрактное val upper: String get() = name.uppercase() // через геттер — можно // val cached: String = "x" // ошибка: поля в интерфейсе запрещены } class User5(override val name: String) : Named2 class Admin2 : Named2 { override val name get() = "админ" // геттер вместо поля } fun main() { println(User5("иван").upper) // ИВАН println(Admin2().name) // админ } ``` Реализовать свойство можно параметром конструктора, хранимым свойством или геттером - интерфейсу это безразлично. ## Множественная реализация ```kotlin interface Readable2 { fun read(): String } interface Writable { fun write(data: String): String } interface Closeable2 { fun close() = "закрыто" } class File2(private val name: String) : Readable2, Writable, Closeable2 { override fun read() = "чтение $name" override fun write(data: String) = "запись '$data' в $name" } fun main() { val f = File2("a.txt") println(f.read()) // чтение a.txt println(f.write("текст")) // запись 'текст' в a.txt println(f.close()) // закрыто } ``` Ограничений на число интерфейсов нет - в отличие от классов. ## Ромбовидное наследование Если два интерфейса дают реализацию одного метода, компилятор требует разрешить конфликт: ```kotlin interface A7 { fun f() = "A" } interface B7 { fun f() = "B" } class C7 : A7, B7 { override fun f() = "${super.f()}${super.f()}" } fun main() { println(C7().f()) } // AB ``` Без `override` класс не скомпилируется: `class C7 : A7, B7` даст ошибку `inherits multiple interface methods`. Требование строгое: `override` нужен, даже если реализацию даёт только **один** из интерфейсов: ```kotlin interface A8 { fun f(): String } // абстрактный interface B8 { fun f() = "B" } // с реализацией // class C8 : A8, B8 // ошибка: inherits multiple interface methods class C8 : A8, B8 { override fun f() = super.f() // выбираем явно } fun main() { println(C8().f()) } // B ``` А вот реализация из **класса** абстрактный метод интерфейса покрывает - здесь писать `override` не нужно: ```kotlin interface Named3 { fun name(): String } abstract class Base3 : Named3 { override fun name() = "из класса" } class Impl10 : Base3(), Named3 // реализация унаследована от Base3 fun main() { println(Impl10().name()) } // из класса ``` Для свойств правило то же: ```kotlin interface A9 { val name: String get() = "A" } interface B9 { val name: String get() = "B" } class C9 : A9, B9 { override val name get() = super.name + super.name } fun main() { println(C9().name) } // AB ``` ## Наследование интерфейсов ```kotlin interface Entity2 { val id: Int } interface Timestamped : Entity2 { val createdAt: Long fun age(now: Long) = now - createdAt } class Record2(override val id: Int, override val createdAt: Long) : Timestamped fun main() { val r = Record2(1, 1000) println("${r.id} ${r.age(1500)}") // 1 500 } ``` Интерфейс может наследовать несколько интерфейсов - ограничения на один супертип здесь нет. ## private-члены ```kotlin interface Validator2 { fun validate(value: String): Boolean private fun isBlank2(value: String) = value.trim().isEmpty() fun check(value: String) = when { isBlank2(value) -> "пусто" validate(value) -> "ок" else -> "неверно" } } class EmailValidator : Validator2 { override fun validate(value: String) = "@" in value } fun main() { val v = EmailValidator() println(v.check(" ")) // пусто println(v.check("a@b.ru")) // ок println(v.check("нет")) // неверно } ``` `private` в интерфейсе - способ вынести вспомогательную логику, не включая её в контракт. `protected` в интерфейсе **запрещён**: все члены либо публичны, либо приватны. ## fun interface - SAM Интерфейс с единственным абстрактным методом можно пометить `fun`, и тогда вместо объекта допустима лямбда: ```kotlin fun interface Transformer { fun transform(input: String): String } fun apply3(value: String, t: Transformer) = t.transform(value) fun main() { println(apply3("abc", Transformer { it.uppercase() })) // ABC println(apply3("abc") { it.reversed() }) // cba val obj = object : Transformer { // можно и так override fun transform(input: String) = "[$input]" } println(apply3("abc", obj)) // [abc] } ``` Для своего кода обычно лучше функциональный тип `(String) -> String` - он не требует объявления интерфейса. `fun interface` нужен, когда: - интерфейсу нужны дополнительные методы с реализацией - API должен быть удобным из Java - имя контракта важно для читаемости ```kotlin fun interface Predicate2 { fun test(value: T): Boolean fun negate(): Predicate2 = Predicate2 { !test(it) } // метод по умолчанию } fun main() { val positive = Predicate2 { it > 0 } println(positive.test(5)) // true println(positive.negate().test(5)) // false } ``` ## Интерфейсы в делегировании ```kotlin interface Logger4 { fun log(msg: String): String } class ConsoleLogger2 : Logger4 { override fun log(msg: String) = "[LOG] $msg" } class Service14(logger: Logger4) : Logger4 by logger { fun work() = log("работаю") } fun main() { println(Service14(ConsoleLogger2()).work()) // [LOG] работаю } ``` Подробнее: [Делегирование](Делегирование.md) ## Интерфейс как граница модуля ```kotlin interface PaymentGateway { fun charge(amount: Long): String } internal class StripeGateway : PaymentGateway { override fun charge(amount: Long) = "Stripe: списано $amount" } internal class TestGateway : PaymentGateway { override fun charge(amount: Long) = "Тест: якобы списано $amount" } fun createGateway(test: Boolean): PaymentGateway = if (test) TestGateway() else StripeGateway() fun main() { println(createGateway(test = true).charge(100)) // Тест: якобы списано 100 println(createGateway(test = false).charge(100)) // Stripe: списано 100 } ``` Это основное практическое применение: клиент зависит от интерфейса, реализации скрыты за `internal` и подменяемы. Подробнее: [Модификаторы видимости](Модификаторы%20видимости.md) > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `interface A { }` | то же | > | `default void f() { }` | `fun f() { }` - слово не нужно | > | `static` методы в интерфейсе | `companion object` в интерфейсе | > | `private` методы (Java 9+) | то же | > | поля `public static final` | только `const` в `companion object` | > | `A.super.f()` | `super.f()` | > | `@FunctionalInterface` | `fun interface` | > | нет свойств | `val`/`var` в интерфейсе | > > **Реализация по умолчанию не требует ключевого слова.** В Java нужно `default`, потому что до версии 8 интерфейсы реализации не имели и слово понадобилось для совместимости. В Kotlin такого ограничения не было. > > **Свойства в интерфейсе** - то, чего в Java нет. Java-интерфейс объявляет геттер как метод, и реализация обязана быть методом: > ```java > interface Named { String getName(); } > ``` > Kotlin позволяет реализовать `val name: String` параметром конструктора - короче и без ручного геттера. > > **Константы в интерфейсе работают иначе.** В Java поле интерфейса неявно `public static final`, что породило антипаттерн «интерфейс-константа». В Kotlin поля в интерфейсе запрещены; константы живут в `companion object`: > ```kotlin > interface Config6 { > companion object { const val MAX = 100 } > } > > fun main() { println(Config6.MAX) } // 100 > ``` > > **Реализация по умолчанию и совместимость с Java.** По умолчанию Kotlin компилирует её в статический метод плюс `DefaultImpls`-класс, а не в Java `default`-метод. Java-классу, реализующему такой интерфейс, придётся реализовать метод самому. Флаг `-Xjvm-default=all` меняет это поведение. Подробнее: [Kotlin из Java](../Взаимодействие%20с%20Java/Kotlin%20из%20Java.md) > > Сравнить: [Интерфейсы в Java](../../Программирование%20на%20Java/ООП/Интерфейсы.md), [Полиморфизм подтипов](../../Программирование%20на%20Java/ООП/Полиморфизм/Полиморфизм%20подтипов.md) > [!warning] Подводные камни > **Свойство в интерфейсе не хранит значение.** Попытка задать значение - ошибка: > ```kotlin > interface Counter2 { > // var count: Int = 0 // ошибка: property initializers are not allowed > var count: Int // только объявление > } > > class Impl8 : Counter2 { override var count = 0 } > > fun main() { > val c = Impl8() > c.count++ > println(c.count) // 1 > } > ``` > > **Геттер в интерфейсе вычисляется у каждой реализации заново** - это не кеш: > ```kotlin > interface Sized { > val items: List > val total: Int get() = items.sum().also { println("считаю") } > } > > class Impl9 : Sized { override val items = listOf(1, 2) } > > fun main() { > val i = Impl9() > println(i.total) > println(i.total) // "считаю" дважды > } > ``` > Кешировать можно только в классе-реализации через `by lazy`. > > **Ромбовидный конфликт не решается автоматически** - нужен явный `override` с `super`. Это строгость, а не недостаток: язык не выбирает реализацию за вас. > > **`protected` в интерфейсе запрещён.** Если нужна защищённая логика для наследников, нужен абстрактный класс. Подробнее: [Абстрактные классы](Абстрактные%20классы.md) > > **Добавление метода в интерфейс - ломающее изменение** для всех реализаций, если у него нет реализации по умолчанию. Для публичного API это означает, что интерфейс стоит проектировать узким. > > **`fun interface` позволяет только один абстрактный метод.** Добавив второй, вы сломаете все вызовы с лямбдой: > ```kotlin > fun interface Handler2 { fun handle(x: Int): String } > > fun main() { > val h = Handler2 { "получено $it" } > println(h.handle(1)) // получено 1 > } > ``` > > **Интерфейс с реализацией по умолчанию - уже не чистый контракт.** Изменение такой реализации меняет поведение всех классов, которые её не переопределили, - то есть это наследование со всеми его рисками. > > **Из Java реализация по умолчанию не видна** без `-Xjvm-default=all`. Java-код будет требовать реализовать метод, который в Kotlin считается необязательным. Дополнительно: [Абстрактные классы](Абстрактные%20классы.md) Далее: [Делегирование](Делегирование.md)