Ce face value class
Dacă o funcție primește (idClient: Long, idComanda: Long), poți inversa ușor argumentele fără nicio eroare. Cu value class creezi tipuri separate, IdClient și IdComanda, iar compilatorul te oprește când le amesteci. La rulare, de cele mai multe ori, obiectul dispare și rămâne doar valoarea din interior, deci nu plătești o alocare în plus.
O value class are exact o proprietate val în constructorul primar. Poate avea blocuri init (pentru validare), metode și proprietăți calculate, poate implementa interfețe, dar nu poate avea alte câmpuri cu stare. Pe JVM se scrie cu adnotarea @JvmInline.
Egalitatea == compară valoarea din interior; === nu este permis. Când este pusă într-o colecție generică sau folosită ca tip nullable, valoarea este „împachetată” într-un obiect, ca un Int într-o List<Int>.
Exemple
Exemplul 1: Tipuri separate pentru identificatori
trimite cere întâi un IdClient, apoi un IdComanda. Inversarea lor ar da eroare de compilare, deși amândouă țin un Long.
@JvmInline
value class IdClient(val valoare: Long)
@JvmInline
value class IdComanda(val valoare: Long)
fun trimite(client: IdClient, comanda: IdComanda) =
println("Comanda ${comanda.valoare} pleacă spre clientul ${client.valoare}")
fun main() {
val client = IdClient(1507)
val comanda = IdComanda(98_001)
trimite(client, comanda)
// trimite(comanda, client) // eroare: argument type mismatch
println(client == IdClient(1507))
}
Rezultat
Comanda 98001 pleacă spre clientul 1507
true
Exemplul 2: Validare și metode
init refuză sumele negative. Lei are operatorul plus și o afișare proprie.
@JvmInline
value class Lei(val bani: Long) {
init { require(bani >= 0) { "Suma nu poate fi negativă: $bani" } }
operator fun plus(alt: Lei) = Lei(bani + alt.bani)
override fun toString() = "%d,%02d lei".format(bani / 100, bani % 100)
}
fun main() {
val total = Lei(1250) + Lei(799)
println("Total: $total")
try {
Lei(-5)
} catch (e: IllegalArgumentException) {
println("Eroare: ${e.message}")
}
}
Rezultat
Total: 20,49 lei
Eroare: Suma nu poate fi negativă: -5
Exemplul 3: Email validat
Un Email poate fi creat doar cu un text care conține @; restul codului știe că adresa a fost verificată.
@JvmInline
value class Email(val adresa: String) {
init { require("@" in adresa) { "Adresă invalidă: $adresa" } }
val domeniu get() = adresa.substringAfter("@")
}
fun creeazaEmail(text: String): Email? = runCatching { Email(text.trim().lowercase()) }.getOrNull()
fun main() {
for (text in listOf("Ana.Pop@Exemplu.ro", "fara-arond", " mihai@iasi.ro ")) {
val e = creeazaEmail(text)
println(if (e == null) "Respins: '$text'" else "Acceptat: ${e.adresa} (domeniu ${e.domeniu})")
}
}
Rezultat
Acceptat: ana.pop@exemplu.ro (domeniu exemplu.ro)
Respins: 'fara-arond'
Acceptat: mihai@iasi.ro (domeniu iasi.ro)
Greșeli frecvente
- Uitarea lui
@JvmInlinepe JVM: eroarevalue classes without '@JvmInline' annotation are not yet supported. - Două proprietăți în constructor (
value class Punct(val x: Int, val y: Int)): nu este permis; foloseștedata class. - Folosirea lui
===pe valorivalue class: nu are sens, pentru că nu au identitate; folosește==.
Comenzi înrudite
data class: data class este o clasă pentru date: primește automat toString, equals, hashCode, copy și componentN.typealias: Dă un nume nou, mai scurt sau mai clar, unui tip existent; nu creează un tip nou.class: class declară o clasă: un tip nou care grupează date (proprietăți) și comportament (metode).operator: operator marchează o funcție cu nume special (plus, get, contains, compareTo…) ca să poată fi folosită prin +, [], in sau <.