Как работают extension-функции в Kotlin?

Extension-функции в Kotlin — это способ расширить функциональность существующих классов без их изменения и без наследования. С их помощью можно добавить "новые методы" к уже существующим классам (включая классы Java), даже если у вас нет доступа к их исходному коду.

📌 Основной синтаксис

fun ТипИмени.имяФункции(параметры): ВозвращаемыйТип {
// тело функции
}

Например:

fun String.addSmile(): String {
return this + " 😊"
}
val result = "Hello".addSmile() // Hello 😊

Здесь "Hello" — обычная String, но мы добавили к ней "метод" addSmile().

⚙️ Как это работает под капотом

  • Extension-функции не изменяют оригинальный класс.

  • На уровне байткода это обычные статические функции, которым первый параметр передаётся неявно как this.

  • Компилятор просто позволяет вам писать более выразительный синтаксис, будто это метод экземпляра.

fun String.addSmile(): String
// на JVM  static String addSmile(String receiver)

🔗 Важные особенности

1. this внутри extension — это объект, к которому применяется функция

fun Int.square(): Int {
return this \* this
}
val x = 5.square() // 25

this в теле функции — это 5.

2. Могут быть доступны только в области видимости, где объявлены

Если вы написали extension в одном файле, его не видно в другом без импорта:

import mypackage.square

3. Не могут переопределять методы

Если в классе уже есть метод с таким именем, extension-функция не заменит его:

class A {
fun hello() = "original"
}
fun A.hello() = "extension"
val a = A()
println(a.hello()) // original

Вызывается метод класса, а не extension-функция.

4. Можно вызывать только явно

Нельзя "расширить" приватный или защищённый метод: this внутри extension-функции — это только публичный API объекта.

Примеры полезных extension-функций

— Для коллекций:

fun <T> List<T>.second(): T? {
return if (this.size > 1) this\[1\] else null
}
val list = listOf("a", "b", "c")
println(list.second()) // b

— Для UI (например, в Android):

fun View.show() { visibility = View.VISIBLE }
fun View.hide() { visibility = View.GONE }
myButton.hide()

— Для Java-классов:

fun File.readTextUtf8(): String = this.readText(Charsets.UTF_8)
val text = File("file.txt").readTextUtf8()

→ Вы расширяете класс из Java, не меняя его код.

📦 Extension-функции vs Inheritance

Подход Наследование Extension-функции
Доступ к private ✅ Да ❌ Нет (только к публичному API)
--- --- ---
Требует изменять класс ✅ Да (новый подкласс) ❌ Нет
--- --- ---
Поддержка полиморфизма ✅ Да ❌ Нет
--- --- ---
Удобство ❌ Более громоздко ✅ Лаконичный синтаксис
--- --- ---

🧠 Итого

Extension-функции позволяют:

  • Добавлять "методы" к существующим классам (в том числе Java).

  • Писать более выразительный и лаконичный код.

  • Делать код чище и более читаемым.

  • Использовать мощные DSL-решения (например, apply, run, also — это тоже extensions).

Но они не изменяют поведение классов и не поддерживают переопределение или доступ к приватным данным. Это просто синтаксический сахар, превращающий обычную статическую функцию в удобный вызов через точку.