W Kotlinie inline class (od wersji Kotlin 1.5 — termin "value class"), pozwalają na tworzenie opakowań dla typów z minimalnymi kosztami. Podczas kompilacji takie klasy są w tle zamieniane na swoją wewnętrzną wartość (value), aby uniknąć kosztów związanych z tworzeniem obiektów.
Ograniczenia i cechy:
=== nie działa jak zwykle).Przykład:
@JvmInline value class UserId(val value: String) fun getUser(id: UserId) { println("Loading user with id: ${id.value}") } val id = UserId("XYZ") getUser(id) // W tle działa po prostu jako String!
Kiedy używać:
Czy można dziedziczyć po value class lub używać go w hierarchii interfejsów/abstrakcyjnych klas?
Odpowiedź: Nie, value class nie może dziedziczyć po innych klasach (z wyjątkiem interfejsów), nie może być otwarta dla dziedziczenia, nie dopuszcza bloku init i innych pól niestatycznych. Jedyną dostępną opcją jest implementacja interfejsów.
Przykład:
interface Validatable { fun isValid(): Boolean } @JvmInline value class Email(val raw: String) : Validatable { override fun isValid() = raw.contains("@") }
Historia
Aplikacja na Androida znacznie wydłużyła czas uruchamiania po dodaniu value class do parametrów Parcelable: okazało się, że niepoprawny @Parcelize z value class prowadził do boxing/unboxing na każdym etapie serializacji, psując zalety inline.
Historia
Mikrousługa zaczęła aktywnie używać value class dla UserId i ProductId w celu zapewnienia bezpieczeństwa typów, ale w wielu miejscach funkcje generyczne wymagały refleksji, która nie działała z "opakowaniem". Testy jednostkowe nagle zaczęły zawodzić, pojawiły się ClassCastException.
Historia
Kod migrowany z Javy zaczął zastępować wewnętrzne klasy domenowe na value class w celu optymalizacji, ale ich użycie jako pól nullable prowadziło do niespodziewanych wyjątków null pointer, ponieważ value class może być null tylko wtedy, gdy zewnętrzna wartość też jest null, co łamało stare inwarianty.