Kotlin предлагает ряд особенностей и преимуществ, что делает его привлекательным для разработчиков.
Одним из достоинств Kotlin, отличающих его от других языков, в том числе Java, является подход к реализации утилитных классов. Утилиты могут быть полезными инструментами в разработке программного обеспечения, хотя и не всегда являются лучшим выбором при проектировании объектно-ориентированных систем.
Как Java-разработчику, вам будет полезно рассмотреть различные способы реализации утилитных классов в Kotlin и узнать, что делается в этом языке “под капотом”. Kotlin предлагает целый ряд различных способов, упрощающих переход от Java, начиная от сокращения стереотипного кода и заканчивая улучшением читаемости кода.
Понимание различий в реализации утилитных классов в Kotlin и Java — важный шаг в освоении такого мощного языка, как Kotlin. Будь вы опытный Java-разработчик, желающий расширить спектр своих навыков, или новичок в программировании, уделите внимание этому аспекту.
Как вам известно, создать утилитный класс в Java довольно просто. Процесс включает следующие шаги.
- Создание терминального класса с ключевым словом
final
(потому что не предполагается определение подкласса) - Добавление закрытого конструктора, чтобы избежать ненужного инстанцирования класса
- Добавление статических утилитных методов (в данном случае
foo
).
public final class UtilClass {
private UtilClass(){}
public static String foo() {
return "bar";
}
}
Теперь, учитывая отсутствие статических методов в Kotlin, посмотрим, как использовать возможности этого языка, чтобы выполнить эту реализацию более простым способом.
3 способа реализации утилитных классов в Kotlin
Объявление объектов
Шаблон “одиночка”, полезный в ряде скриптов, реализуется в Kotlin удобным способом.
object UtilObject {
fun foo(): String = "bar"
}
Этот блок кода создаст (лениво инициализированный) объект-одиночку, который доступен из любого места в коде без необходимости дополнительного инстанциирования.
Перед рассмотрением Java-эквивалента этого кода стоит упомянуть интероперабельность Kotlin и Java.
Когда вы пишете код Kotlin, “под капотом” он компилируется в байт-код Java, который может выполняться на виртуальной Java-машине (JVM) так же, как и код Java. Это означает, что Kotlin можно использовать для проектирования приложений, которые работают на любой платформе, поддерживающей JVM, включая настольные, веб- и мобильные платформы.
Kotlin был разработан как язык, полностью совместимый с Java, из чего следует, что Kotlin-код может вызывать Java-код и наоборот. Это облегчает постепенный перенос кодовых баз Java на Kotlin и использование Kotlin наряду с существующим Java-кодом.
Примечание: Java-эквиваленты кода, демонстрируемые в этой статье, были созданы путем компиляции Kotlin в байт-код Java и последующей декомпиляции в Java.
Посмотрим, как компилятор Kotlin сгенерировал Java-эквивалент кода:
public final class UtilObject {
@NotNull
public static final UtilObject INSTANCE;
@NotNull
public final String foo() {
return "bar";
}
private UtilObject() {
}
static {
UtilObject var0 = new UtilObject();
INSTANCE = var0;
}
}
Java-класс создается с использованием переменной экземпляра и закрытого конструктора. Благодаря этому класс инстанцируется только один раз и становится доступным в любом месте в коде.
Применение будет выглядеть следующим образом:
UtilObject.foo()
Однако Kotlin значительно упрощает этот процесс, позволяя достичь того же результата всего тремя строками кода.
Объекты-компаньоны
Объекты-компаньоны (companion objects) — это также объекты-одиночки, у которых есть переменные и функции, связанные с классом, но не с конкретными его экземплярами.
Вместо экземпляра класса создается другой класс, получающий имя Companion (или любое другое), и в него помещаются все свойства и функции.
class UtilClass {
companion object{
fun foo(): String = "bar"
}
}
Посмотрим на Java-эквивалент кода, сгенерированный компилятором Kotlin:
public final class UtilClass {
@NotNull
public static final Companion Companion = new Companion(null);
public static final class Companion {
@NotNull
public final String foo() {
return "bar";
}
private Companion() {
}
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
Как видим, создаются два отдельных Java-класса. Первый из них хранит экземпляр объекта-компаньона в статической переменной.
Второй (Companion) представляет собой быстро инициализированный объект-одиночку, то есть уникальный экземпляр, к которому можно получить доступ без вызова конструктора. Это позволяет объекту-компаньону быть видимым и доступным в любом месте.
В этом случае применение будет выглядеть следующим образом:
UtilClass.foo()
Функции внутри пакета
Функции внутри пакета, также известные как функции верхнего уровня, объявляются непосредственно в файле без содержания в классе. Обычно они используются для определения констант и функций, которые должны быть независимы от конкретного класса.
Utils.kt:
package com.yourdomain.utils
fun foo(): String = "bar"
Посмотрим Java-эквивалент кода, созданный компилятором Kotlin:
public final class UtilPackageKt {
@NotNull
public static final String foo() {
return "bar";
}
}
Итак, сгенерирован терминальный Java-класс, а утилитная функция foo
стала статической функцией в отличие от других вариантов.
Согласно документации Kotlin, функции верхнего уровня рекомендуются как решение для большинства случаев, когда необходимы статические функции.
Использование будет выглядеть следующим образом:
foo()
В отличие от других вариантов, здесь не нужно использовать ссылку класса, чтобы вызвать функцию. Вместо этого можно импортировать и использовать функцию без пространства имен.
Из-за отсутствия пространства имен может показаться, будто вы используете функции-призраки, источник которых неизвестен, а также ухудшиться читаемость кода.
Заключение
Выбор метода зависит от конкретной задачи. При этом не всегда легко определить, какой метод является лучшим. Каждая ситуация должна оцениваться отдельно.
Итак, вы ознакомились с различными возможностями языка Kotlin и узнали, как Java обрабатывает их “под капотом”. Теперь у вас будет более четкое представление о сходствах и различиях вариативных реализаций утилитных функций в Kotlin.
Выделим главное:
- Если нужно создать потокобезопасные, лениво инициализируемые объекты-одиночки, может подойти объявление объектов.
- Если нужно создать быстро инициализируемое, “Java static-подобное” одиночное расширение класса, могут подойти объекты-компаньоны.
- Если нужно создать “реальные Java static” функции (без экземпляра) без необходимости использования пространства имен, могут подойти функции внутри пакета.
Читайте также:
- Запечатанный класс
- Объекты данных в Kotlin
- Реализация структурированной конкурентности в Java и Kotlin
Читайте нас в Telegram, VK и Дзен
Перевод статьи Baturay Ucer: Utility classes in Kotlin: A Java Developer’s Perspective