ПрограммированиеBackend разработчик

Как работает механизм visibility modifiers (internal/private/protected/public) для top-level функций и свойств в Kotlin? Какие отличия с Java и какие тонкости следует учитывать?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

В Kotlin модификаторы видимости позволяют контролировать доступ к объявлениям: классам, свойствам, функциям и топ-левел (на уровне файла) сущностям. В отличие от Java, где модификаторы действуют только на уровне класса, в Kotlin они работают и для top-level объявлений, что важно для структурирования больших проектов и library API.

История вопроса

В Java нет модификаторов видимости для функций или свойств вне класса — всё находится внутри public (или package-private) класса. В Kotlin принято структурировать проект по своему, часто функция или свойство находятся не внутри класса, а прямо в файле.

Проблема

Часто разработчики Java ожидают, что public по умолчанию работает так же, как в Java, но в Kotlin top-level function (или свойство) видно во всех модулях, если не помечено иначе. Некорректное определение видимости может привести к лексическому засорению публичного API, неожиданной доступности внутренних утилит, либо к недоступности нужных публичных функций.

Решение

В Kotlin доступны следующие модификаторы:

  • public: объявление видно везде (является модификатором по умолчанию для top-level).
  • internal: объявление видно во всех файлах того же модуля (одного gradle-модуля, одного компилируемого артефакта, одного jar).
  • private: видно только в том же файле/классе, где объявлено. Для top-level - только внутри файла.
  • protected: неприменим для top-level объявлений, только у классов/интерфейсов и их наследников.

Пример:

// file: Foo.kt private fun utilityFun() {} internal val bar: Int = 10 public val baz: Int = 20 // public не обязателен fun printValue() { println(bar) }

Ключевые особенности:

  • internal ограничивает видимость модулем (jar/artifact), а не пакетом.
  • protected не может использоваться для top-level функций или свойств.
  • private в top-level ограничивает объявление рамками текущего файла.

Вопросы с подвохом.

Можно ли использовать protected для top-level функции?

Нет, protected релевантен только членам класса/интерфейса, top-level такие элементы не поддерживают.

Если объявить top-level функцию с internal, будет ли она видна внутри других модулей?

Нет. Она будет видна только в пределах текущего jar/Gradle-модуля.

Чем отличается private class от private top-level function?

  • private class: виден только внутри текущего файла, не может быть использован вне файла.
  • private top-level function или свойство: аналогично видимо только внутри файла.

Пример:

// file: Utils.kt private fun helper() { /* ... */ } // виден только в этом файле internal fun useful() { /* ... */ } // виден во всём модуле

Типовые ошибки и анти-паттерны

  • Использование public по умолчанию для всех объявлений приводит к "засорению" автокомплита и API.
  • Использование internal для library, предназначенной для внешних клиентов, скрывает нужные public API.
  • Путаница с protected и попытки применять их top-level.

Пример из жизни

Негативный кейс

Тестовые утилиты объявлены public и так попадают в artefact, мешая клиенту библиотеки — становится видно всё, что не относится к public API.

Плюсы:

  • Быстрая интеграция.

Минусы:

  • Растёт размер публичного API, появляются "случайные" методы в доступе.

Позитивный кейс

Внутренние функции объявлены private, утилиты с internal видимостью для общего использования внутри модуля, только тщательно продуманные интерфейсы имеют public-доступ.

Плюсы:

  • Ясная, чистая структура API.
  • Минимизируются случайные зависимости.

Минусы:

  • Необходимость продумывать структуру проекта.