Classe Any
Come già accennato nella lezione 2 in Kotlin ogni classe, in maniera implicita o meno estende la classe Any. Le due sintassi sono uguali:
class Employee : Any()
class Employee
Esplicitando o meno l'ereditarietà tramite : Any() il risultato non cambia. La classe Any offre alcuni metodi targati con la keyword open:
equals() | open operator fun equals(other: Any?): Boolean |
---|---|
hashCode() | open fun hashCode(): Int |
toString() | open fun toString(): String |
Per esempio in una classe per contenere il numero di telefono ha senso che il metodo toString() restituisce il numero di telefono nella forma (xxx) xxx-xxxx. Come che il metodo equals() esegua un confronto fra due istanze e non tra i due riferimenti. Guardiamo la seguente classe per capire l'utilità:
class PhoneNumber(areaCode: Int, exchange: Int, extension: Int) {
private val areaCode: Short
private val exchange: Short
private val extension: Short
init {
rangeCheck(areaCode, 999, "area code")
rangeCheck(exchange, 999, "exchange")
rangeCheck(extension, 9999, "extension")
this.areaCode = areaCode.toShort()
this.exchange = exchange.toShort()
this.extension = extension.toShort()
}
private fun rangeCheck(arg: Int, max: Int, name: String) {
if (arg < 0 || arg > max)
throw IllegalArgumentException(name + ": " + arg)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as PhoneNumber
if (areaCode != other.areaCode) return false
if (exchange != other.exchange) return false
if (extension != other.extension) return false
return true
}
override fun hashCode(): Int {
var result: Int = areaCode.toInt()
result = 31 * result + exchange
result = 31 * result + extension
return result
}
override fun toString(): String {
return "($areaCode) $exchange-$extension"
}
}
data class
Kotlin, per ovviare alla mancanza di strutture dati utili alla memorizzazione di dati nella JVM (per esempio le struct del C# o Swift) ha introdotto le data class:
Una data class in Kotlin:
data class Person(val name: String, val email: String, val age: Int)
Fine, basta aggiungere la parola chiave data alla dichiarazione della classe.
Vediamo cosa succede quando richiamiamo la classe:
fun main(args: Array) {
val person: Person = Person("matteo", "[email protected]", 28)
println(person.name)
println(person.toString())
}
Abbiamo anche richiamato il metodo toString(), senza farne l'override, con questo bel risultato:
matteo Person(name=matteo, [email protected], age=28)
Ma senza la parola data e rilanciare il main otteniamo:
matteo
com.kotlin.test.Person@610455d6
Come vedete il risultato è completamente diverso. In nelle data class c'è l'override automatico per il metodo toString() (e non solo!). Ovviamente possiamo fare molto di più con queste classi:
- Impostare alcuni campi come mutabili ed altri no:
data class Person(var name: String, var email: String, val dateOfBorn : Date)
- Copiare l'oggetto in nuovo oggetto (le data class di Kotlin ci permettono di realizzare delle copie dell'istanza tramite la funzione copy()). Kotlin implementa la funzione copy() nel seguente modo:
fun copy(name: String = this.name, email: String = this.email, age: Int = this.age) = Person(name, email, age)
- Ricordo che nessuno ci impedisce di modificare la funzione copy(). Fatto sta che un tipico utilizzo è il seguente:
val jack = User(name = "Jack", email = [email protected], age = 1)
val olderJack = jack.copy(age = 2)
- Realizzare costruttuti secondari, sempre con questo formalismo: constructor(name: String, age: Int) : this(name)
- Destrutturare l'oggetto (non preoccupatevi dalle cose strane, le vedremo in una prossima lezione)
val jane = Person("Jane", "[email protected]", 35)
val (name, email, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
Le data class di Kotlin offrono le seugenti agevolazioni implicite:
ovverride automatico dei metodi della classe Any: toString(), hashCode() e equals();
- generazione automatica del metodo copy();
- generazione automatica dei metodi per destrutturare l'oggetto: component1(), component2(), ..., componentN();
Nota: Le data class ci obbligano a specificare se il campo è una val o una var, inoltre il costruttore primario richiede almeno un parametro e la classe non estendere altre classi (ma può implementare interfacce ) e non può essere avere i seguenti modificatori: open, abstract, sealed o inner.