Как язык программирования, Kotlin предлагает ряд функций и преимуществ, которые делают его привлекательным выбором для разработчиков.

Одним из аспектов Kotlin, который отличает его от других языков, таких как Java, является его подход к классам служебных. Утилиты могут быть полезным инструментом при разработке программного обеспечения, даже если они не всегда являются лучшим выбором, когда речь идет о проектировании объектно-ориентированных систем.

В этой статье мы рассмотрим различные способы реализации служебных классов в Kotlin и то, что на самом деле делает Kotlin под капотом с точки зрения разработчика Java. Kotlin предлагает ряд различных способов, от сокращения стандартного кода до улучшения читабельности кода, которые могут сделать переход с Java более плавным.

Независимо от того, являетесь ли вы опытным разработчиком Java, стремящимся расширить свой набор навыков, или новичком в мире программирования, понимание различий между реализацией служебных классов в Kotlin и Java является важным шагом в освоении этого мощного языка.

Как мы все знаем, процесс создания служебного класса в Java довольно прост и описан ниже:

  • Создайте окончательный класс (потому что он не должен быть подклассом)
  • Добавьте частный конструктор, чтобы избежать ненужного создания экземпляра класса.
  • Добавьте статические служебные методы (в этом случае foo)
public final class UtilClass {
    private UtilClass(){}
    
    public static String foo() {
        return "bar";
    }
}

Теперь, учитывая отсутствие статических методов в Kotlin, давайте рассмотрим, как мы можем применить новые функции, которые предоставляет Kotlin, чтобы реализовать этот код более простым способом.

Kotlin предоставляет 3 различных способа реализовать такую ​​потребность:

  1. Объявления объектов.Шаблон Singleton может быть полезен в ряде сценариев, а Kotlin предоставляет удобный способ реализации синглетонов.
object UtilObject {
    fun foo(): String = "bar"
}

Блок кода будет генерировать (лениво инициализированный) одноэлементный объект, к которому можно получить доступ и использовать из любого места в коде без необходимости какого-либо дополнительного создания экземпляра.

Прежде чем мы углубимся в Java-эквивалент кода, я хотел бы немного рассказать вам о Kotlin Java Interoperability.

Совместимость Kotlin-Java

Когда вы пишете код Kotlin, внутри он компилируется в байт-код Java, который может работать на виртуальной машине Java (JVM) точно так же, как код Java. Это означает, что Kotlin можно использовать для разработки приложений, работающих на любой платформе, поддерживающей JVM, включая настольные, веб- и мобильные платформы.

Kotlin был разработан для полной совместимости с Java, что означает, что код Kotlin может вызывать код Java и наоборот. Это позволяет разработчикам постепенно переносить свои кодовые базы Java на Kotlin или использовать Kotlin вместе с существующим кодом Java.

Примечание. Эквиваленты кода Java, которые мы увидим в статье, были созданы путем компиляции kotlin в байт-код Java и последующей декомпиляции его в Java.

Давайте посмотрим на сгенерированный Java-эквивалент кода компилятором Kotlin:

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 значительно упрощает этот процесс, позволяя нам добиться того же результата всего тремя строками кода.

2. Объекты-компаньоны.Объекты-компаньоны также являются одноэлементными объектами, которые имеют переменные или функции, связанные с классом, но не с конкретными экземплярами этого класса.

Вместо этого он создает другой класс с именем 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. Первый хранит экземпляр объекта-компаньона в статической переменной.

Второй («Компаньон») — это (с нетерпением инициализированный) одноэлементный объект, что означает уникальный экземпляр, к которому можно получить доступ без вызова конструктора. Это позволяет объекту-компаньону быть видимым и доступным из любого места.

В этом случае использование по-прежнему будет выглядеть так:

UtilClass.foo()

3. Функции уровня пакета: функции уровня пакета, также известные как функции верхнего уровня, объявляются непосредственно в файле, а не внутри класса. Обычно он используется для определения констант или функций, которые должны быть независимыми от какого-либо конкретного класса.

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.
  • Рассмотрите возможность использования функций уровня пакета, если вам нужно создать «настоящие статические Java-функции» (без экземпляра) без использования пространства имен.

Спасибо за прочтение, надеюсь, вам понравилось.

Предложения и отзывы всегда приветствуются.