programowanieProgramista Backend

Czym są klasy inline (value classes) w Kotlinie, do czego są używane i jakie są ograniczenia ich stosowania?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania

W Kotlinie wprowadzono koncepcję klas inline/value (obecnie nazywanych klasami wartości), aby zminimalizować overhead czasu wykonania związany z używaniem opakowań dla typów prymitywnych i małych struktur. Pomysł został zaczerpnięty z innych języków (np. struktury C#), w których taka optymalizacja jest użyteczna do zwiększenia wydajności bez utraty typizacji.

Problem

Tworzenie klas opakowujących (np. dla typów-encji lub identyfikatorów) bez optymalizacji prowadzi do powstawania dodatkowego obiektu w pamięci, co wpływa na wydajność, GC i może prowadzić do kosztów związanych z Boxing/Unboxing. Często pojawia się potrzeba posiadania ścisłej typizacji (np. UserId zamiast Int), ale bez rzeczywistego tworzenia obiektów.

Rozwiązanie

Klasa wartości jest deklarowana z modyfikatorem value. W większości sytuacji JVM nie tworzy dodatkowego obiektu — klasa wartości jest zastępowana swoim polem bezpośrednio (inlining). Daje to bezpieczeństwo typów i wydajność zbliżoną do „po prostu Int”.

Przykład kodu:

@JvmInline value class UserId(val value: Int) fun showId(id: UserId) = println(id.value) val id = UserId(15) showId(id) // Bez tworzenia osobnego obiektu UserId

Kluczowe cechy:

  • Klasa musi zawierać dokładnie jedną właściwość (val lub var), ale ten typ nie może być nullable
  • Brak wsparcia dla dziedziczenia (klasa wartości nie może dziedziczyć ani być dziedziczona)
  • Niektóre ograniczenia: nie można mieć bloku init, przechowywać właściwości lateinit lub niezinicjowanych, nie można używać refleksji we wszystkich przypadkach.

Pytania podchwytliwe.

Czy klasy wartości mogą mieć kilka właściwości?

Nie, klasa wartości może zawierać tylko jedną właściwość.

// Błąd: // value class Money(val amount: Int, val currency: String)

Czy można stworzyć klasę wartości z właściwością nullable?

Pole value w klasie wartości nie może być nullable — tylko typy nie-nullable.

// Błąd: // value class Name(val value: String?)

Czy można używać dziedziczenia z klasami wartości?

Klasa wartości nie wspiera dziedziczenia i nie może być abstrakcyjna ani sealed.

// Błąd: // value class NewId(val value: Int): BaseId()

Typowe błędy i antywzorce

  • Próba użycia klasy wartości dla złożonych struktur (np. z wieloma polami)
  • Przechowywanie wartości nullable przez klasy wartości
  • Używanie metod referencyjnych (np. metody equals/hashCode mogą zachowywać się nieprzewidywalnie w skompilowanym backendzie)

Przykład z życia

Negatywny przypadek

Programista stworzył klasę wartości dla encji z dwiema właściwościami (np. para Int i String), otrzymał błąd kompilacji.

Zalety:

  • Próbuje osiągnąć ścisły typ Wady:
  • Nie działa, niemożliwa kompilacja

Pozytywny przypadek

Programista używa klasy wartości dla typu identyfikatora z jednym polem (np. UserId), co działa szybko i bezpiecznie.

Zalety:

  • Zwięzły i bezpieczny kod
  • Brak overheadu czasu wykonania Wady:
  • Można używać tylko z jednym typem nie-nullable