[Kotlin] Поверніть мені static

pic

У Kotlin немає ключового слова static, яке є в Java. Замість цього є така конструкція як companion object, і якщо вам потрібно, щоб змінні або методи працювали так, як у Java з static, ви можете оголосити їх у блоці companion object. Це знання вже є у кожного, хто вивчав Kotlin.

Але ось питання — чому ж, власне, відмовились від static? Якщо ви працювали з Java і тільки почали знайомитись з Kotlin, це питання напевно виникне. Адже static здається більш зручним? І навіть ключове слово у Kotlin довше, і його набирати менш зручно.

Чому ж розробники Kotlin відмовились від static у Java? Я намагався знайти чітке пояснення цієї причини, але не зміг знайти відповідної статті, тому вирішив самостійно подумати і поділитись своїми висновками.

Java — static

Я не буду детально пояснювати, яку роль виконує ключове слово static у Java та як воно працює, адже існує багато чудових статей на цю тему. Вкратце, static дозволяє отримати доступ до методу або змінної без створення екземпляра класу.

Основні випадки використання static в Java:

1. Статичні методи (Static Methods)  
2. Статичні змінні (Static Variables)  
3. Статичні блоки ініціалізації (Static Initialization Blocks)  
4. Статичні внутрішні класи (Static Nested Classes)  
5. Визначення утилітарних класів  
6. Реалізація патерну Singleton

Тепер давайте розглянемо, як ці випадки використання static можна реалізувати в Kotlin. Спочатку зупинимось на пунктах 4-6.

Статичні внутрішні класи (Static Nested Classes)

У Java статичні внутрішні класи оголошуються наступним чином:

public class OuterClass {  

 public static class StaticNestedClass {  
 void method() {  
 // Немає доступу до змінних OuterClass  
 }  
 }  
}

У Kotlin це можна виразити так:

class OuterClass {  

 class StaticNestedClass {  
 fun method() {  
 // Немає доступу до змінних OuterClass  
 }  
 }  
}

У Kotlin, якщо клас оголошений внутрішнім класом, він поводиться так само, як статичний внутрішній клас у Java. (Якщо це здатися неприязно, краще думати про це як про протилежне Java).

Визначення утилітарного класу

У Java класи, які мають утилітарні методи, часто виглядають так:

public final class StringUtils {  
 private StringUtils() {} // Запобігаємо інстанціюванню  

 public static String reverse(String str) {  
 return new StringBuilder(str).reverse().toString();  
 }  

 public static boolean isEmpty(String str) {  
 return str == null || str.trim().length() == 0;  
 }  
}

В Kotlin немає необхідності у таких утилітарних класах. Метод можна просто оголосити як топ-левел функцію:

fun reverse(str: String?): String {  
 return StringBuilder(str).reverse().toString()  
}  

fun isEmpty(str: String): Boolean {  
 return str.trim { it <= ' ' }.isEmpty()  
}

Реалізація патерну Singleton

Ось один із способів реалізувати Singleton в Java:

public class Singleton {  
 private static Singleton instance;  

 private Singleton() {}  

 public static Singleton getInstance() {  
 if (instance == null) {  
 instance = new Singleton();  
 }  
 return instance;  
 }  
}

А у Kotlin це можна реалізувати значно простіше:

object Singleton { }

Kotlin має ключове слово object, яке дозволяє дуже легко створювати синглтон-класи. (Це саме те, що є головною темою цієї статті, і чому це так, я поясню пізніше).

Спочатку ключове слово object мені здавалося зайвим. У Java для створення синглтона потрібно писати всі ці рядки коду, що виглядає громіздко. Kotlin же з допомогою object скорочує цю частину коду, і це виглядає простіше. Але чому ми не використовуємо назву Singleton, а просто object?

У документації Kotlin є фраза “все є об'єктом” (everything is an object), що є частиною філософії Kotlin. Тому ключове слово object — це частина цієї концепції. Воно дозволяє об'єднати клас і синглтон в одну сутність без зайвих розділень. Клас і синглтон інстанс більше не потребують окремого визначення, а просто зводяться до "одного об'єкта".

Тепер, коли ми розглянули приклади з Java, подивімося на шість основних застосувань static:

1. Статичні методи (Static Methods)  
2. Статичні змінні (Static Variables)  
3. Статичні блоки ініціалізації (Static Initialization Blocks)  
4. Статичні внутрішні класи (Static Nested Classes)  
5. Визначення утилітарних класів  
6. Реалізація патерну Singleton

Якщо подивитись на пункти 4-6, то в Kotlin вони вже не використовуються. А якщо звернути увагу на пункти 1-3, то їх спільною рисою є те, що методи чи змінні повинні бути прив'язані до певного класу. Точніше, використання ключового слова static передбачає, що змінні або методи за замовчуванням належать певному класу.

І тут ми згадуємо про object, що було введено в Kotlin. При створенні синглтона за допомогою object або при використанні статичних методів та змінних в Kotlin, ми можемо уникнути використання static, щоб забезпечити зручність і узгодженість в програмуванні.

Таким чином, Kotlin відмовляється від static і вибирає object як єдиний механізм для створення і управління такими концепціями.
Як ось тут:

class CompanionObjectClass {  
 object Companion {  
 val text = "Hello, World!"  
 fun printText() {  
 println(text)  
 }  
 }  
}

Як ви можете побачити, всередині класу створюється об'єкт за допомогою object, і цей об'єкт використовується як компаньйон класу. У випадках 1–3, це явно повинно бути прив'язано до певного класу, і з огляду на філософію Kotlin, не хочеться використовувати ключове слово static, яке створює неприємне відчуття, що це "член, який існує в певному статичному просторі, а не пов'язаний із класом". Використання object тут виглядає дуже гарно.

Якщо ви оголосите синглтон-об'єкт всередині класу за допомогою object, ви зможете використовувати його наступним чином:

CompanionObjectClass.Companion.text  
CompanionObjectClass.Companion.printText()

Оскільки ви завжди використовуєте його разом з "компаньйоном", я вирішив дати йому ім'я Companion. Але постійно писати так — трохи незручно. Що, якщо зробити окремий ключовий термін для цього?

kotlin — companion object

class CompanionObjectClass {  
 companion object {  
 val text = "Hello, World!"  
 fun printText() {  
 println(text)  
 }  
 }  
}

Тепер ми отримуємо добре знайомий вигляд. Як ви вже знаєте, в такому випадку можна скористатися цим досить просто:

CompanionObjectClass.text  
CompanionObjectClass.printText()

Я довго думав, чому ж було введено companion object, і мої роздуми розтягнулись на кілька сторінок. Якщо підсумувати:

  • Враховуючи додаткові можливості та філософію проектування Kotlin, static з Java не є обов'язковим.
  • Натомість було введено окремий ключовий термін, щоб справді "тільки тісно пов'язані з класом статичні функції" можна було б вмістити саме в цьому місці, природно поводячись як єдиний об'єкт, пов'язаний з класом.

Тепер я розумію, що в Kotlin static вже не потрібен.

Перекладено з: [Kotlin] 돌려줘요 static

Leave a Reply

Your email address will not be published. Required fields are marked *