Ответ 1
Функции расширения полезны в нескольких случаях и обязательны для других:
Идиоматические случаи:
-
Если вы хотите улучшить, расширить или изменить существующий API. Функция расширения - это идиоматический способ изменить класс, добавив новые функции. Вы можете добавить функции расширения и свойства расширения. См. Пример в Jackson-Kotlin Module для добавления методов в класс
ObjectMapper
, упрощающий обработкуTypeReference
и дженериков. -
Добавление нулевой безопасности к новым или существующим методам, которые нельзя вызвать в
null
. Например, функция расширения для String изString?.isNullOrBlank()
позволяет вам использовать эту функцию даже в строкеnull
, не выполняя сначала свою проверкуnull
. Сама функция выполняет проверку перед вызовом внутренних функций. См. документация для расширений с помощью Nullable Receiver
Обязательные случаи:
-
Если вы хотите встроенную функцию по умолчанию для интерфейса, вы должны использовать функцию расширения, чтобы добавить ее в интерфейс, потому что вы не можете сделать это в объявлении интерфейса (встроенные функции должны быть
final
, которые в настоящее время отсутствуют разрешено внутри интерфейса). Это полезно, когда вам нужны встроенные функции reified, например, этот код из Injekt -
Если вы хотите добавить поддержку
for (item in collection) { ... }
к классу, который в настоящее время не поддерживает это использование. Вы можете добавить метод расширенияiterator()
, который следует правилам, описанным в для документации циклов - даже возвращенный объект, похожий на итератор, может использовать расширения для удовлетворения правил предоставленияnext()
иhasNext()
. -
Добавление операторов в существующие классы, такие как
+
и*
(специализация №1, но вы не можете сделать это каким-либо другим способом, поэтому обязательно). См. документация для перегрузки оператора
Дополнительные случаи:
-
Вы хотите управлять областью определения, когда что-то видимо вызывающему, поэтому вы расширяете класс только в том контексте, в котором вы разрешаете вызов быть видимым. Это необязательно, потому что вы можете просто позволить расширениям видеть всегда. см. ответ в другом вопросе SO для определения функций расширения
-
У вас есть интерфейс, который вы хотите упростить для реализации, но при этом предоставляете пользователю более удобные вспомогательные функции. Вы можете дополнительно добавить методы по умолчанию для интерфейса, чтобы помочь, или использовать функции расширения для добавления частей интерфейса, которые не подлежат ожиданию. Один позволяет переопределять значения по умолчанию, другой - нет (кроме приоритета расширений против членов).
-
Если вы хотите связать функции с категорией функциональности; функции расширения используют свой класс приемников в качестве места для поиска. Их пространство имен становится классом (или классами), из которого они могут быть запущены. В то время как функции верхнего уровня будут сложнее найти и заполнит глобальное пространство имен в диалоговом окне завершения кода IDE. Вы также можете исправить существующие проблемы пространства имен библиотеки. Например, в Java 7 у вас есть класс
Path
, и трудно найти методFiles.exist(path)
, потому что это имя задано странно. Вместо этого функция может быть помещена непосредственно наPath.exists()
. (@kirill)
Правила приоритета:
При расширении существующих классов соблюдайте правила приоритета. Они описаны в KT-10806 как:
Для каждого неявного приемника в текущем контексте мы пытаемся использовать члены, затем локальные функции расширения (также параметры, которые имеют тип функции расширения), а затем нелокальные расширения.