Italian (Italiano) translation by Loris Pizii (you can also view the original English article)
Kotlin è un moderno linguaggio di programmazione che compone il bytecode di Java. È libero e open source e promette di rendere codifica per Android ancora più divertente.
Nell'articolo precedente, hai imparato su classi e oggetti in Kotlin. In questo tutorial, continueremo a saperne di più sulle proprietà e guardiamo anche in tipi avanzati di classi in Kotlin esplorando quanto segue:
- proprietà inizializzate in ritardo
- inline proprietà
- proprietà di estensione
- dati, enum, nidificati e classi sigillate
1. Proprietà in fase inizializzata
Possiamo dichiarare una proprietà non nullo in Kotlin come inizializzato in ritardo. Ciò significa che una proprietà non nullo non verrà inizializzata in fase di dichiarazione con un'inizializzazione valore-reale non avviene per nessun costruttore, ma invece verrà inizializzata in ritardo con un metodo o un'infrarenza di dipendenza.
Guardiamo un esempio per capire questo modificatore di proprietà univoco.
class Presenter { private var repository: Repository? = null fun initRepository(repo: Repository): Unit { this.repository = repo } } class Repository { fun saveAmount(amount: Double) {} }
Nel codice di cui sopra abbiamo dichiarato una proprietà di repository
mutable mutevole che è di tipo Repository
all'interno del presentatore
di classe e abbiamo quindi inizializzato questa proprietà a null durante la dichiarazione. Abbiamo un metodo initRepository ()
nella classe Presentatore
che reinizializza questa proprietà in un secondo momento con un'istanza repositiva
reale. Si noti che questa proprietà può anche essere assegnata a un valore utilizzando un iniettore di dipendenza come Dagger.
Ora per noi invocare metodi o proprietà su questa proprietà del repository
, dobbiamo fare un controllo null o utilizzare l'operatore di chiamate di sicurezza. Perché? Poiché la proprietà del repository
è di tipo nullable (Repository?)
. (Se avete bisogno di un rinfrescante per nullabilità a Kotlin, visita gentilmente Nullability, Loops e Condizioni).
// Inside Presenter class fun save(amount: Double) { repository?.saveAmount(amount) }
Per evitare di eseguire controlli nulli ogni volta che abbiamo bisogno di invocare il metodo di una proprietà, possiamo contrassegnare questa proprietà con il modificatore del latteinsene
. Ciò significa che abbiamo dichiarato che la proprietà (che è un'istanza di un'altra classe) come inizializzato in ritardo (cioè la proprietà verrà inizializzata in seguito).
class Presenter { private lateinit var repository: Repository //... }
Adesso, finché aspettiamo che la proprietà sia stata assegnata un valore, siamo sicuri di accedere ai metodi della proprietà senza eseguire controlli nulli. L'inizializzazione della proprietà può avvenire sia in un metodo setter che tramite l'iniezione di dipendenza.
repository.saveAmount(amount)
Si noti che se si tenta di accedere ai metodi della proprietà, prima è stato inizializzato, avremo un kotlin. UninitializedPropertyAccessException
invece di NullPointerException
. In questo caso, il messaggio di eccezione sarà "repository di proprietà lateinit non è stato inizializzato".
Notare anche le seguenti restrizioni imposte quando ritarda l'inizializzazione di proprietà con i lateinit:
- Deve essere mutevole (dichiarato con
var
). - Il tipo di proprietà non può essere un tipo primitivo, ad esempio
Int
,Double
,Float
e così via. - La proprietà non può avere un getter personalizzato o setter.
2. Proprietà in linea
Nelle funzioni avanzate, ho introdotto il modifier inline
per funzioni di ordine superiore: questo aiuta a ottimizzare le funzioni di ordine superiore che accettano un lambda come parametro.
In Kotlin possiamo anche utilizzare questo modificatore in linea
sulle proprietà. L'utilizzo di questo modificatore ottimizza l'accesso alla proprietà.
Vediamo un esempio pratico.
class Student { val nickName: String get() { println("Nick name retrieved") return "koloCoder" } } fun main(args: Array<String>) { val student = Student() print(student.nickName) }
Nel codice precedente, abbiamo una proprietà normale, soprannome
, che non presentano il modificatore inline.
Se abbiamo decompilare il frammento di codice, utilizzando la funzionalità Visualizza Kotlin Bytecode (se siete in IntelliJ IDEA o Android Studio, utilizzare strumenti > Kotlin > Visualizza Kotlin Bytecode), vedremo il codice Java riportato di seguito:
public final class Student { @NotNull public final String getNickName() { String var1 = "Nick name retrieved"; System.out.println(var1); return "koloCoder"; } } public final class InlineFunctionKt { public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); Student student = new Student(); String var2 = student.getNickName(); System.out.print(var2); } }
Nel codice Java generato sopra (alcuni elementi del codice generato sono stati rimossi per brevità), si può vedere che, all'interno del metodo Main (),
il compilatore ha creato un oggetto Student
, chiamato il metodo getNickName()
e quindi stampato il valore restituito.
Ora specifichiamo la proprietà come in linea
e confrontiamo il codice di byte generato.
// ... inline val nickName: String // ...
Basti inserire il modificatore in linea
prima del modificatore di variabile: var
o val
. Ecco il codice bytecode generato per questa proprietà inline:
// ... public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); Student student = new Student(); String var3 = "Nick name retrieved"; System.out.println(var3); String var2 = "koloCoder"; System.out.print(var2); } // ...
Ancora un certo codice è stato rimosso, ma la chiave da notare è il metodo principale ()
. Il compilatore ha copiato il corpo della funzione get ()
e incollato nel sito di chiamata (questo meccanismo è simile alle funzioni inline).
Il nostro codice è stato ottimizzato perché non è necessario creare un oggetto e chiamare il metodo getter proprietà. Ma, come discusso nelle funzioni di inline funzione, avremmo un bytecode più grande di prima - quindi utilizzare con cautela.
Notare anche che questo meccanismo funzionerà per le proprietà che non dispongono di un campo di backup (ricorda che un campo di backup è solo un campo utilizzato dalle proprietà quando si desidera modificare o utilizzare i dati di campo).
3. Proprietà estensione
Nelle funzioni avanzate ho anche discusso le funzioni di estensione — questi ci danno la possibilità di estendere una classe con nuove funzionalità senza dover ereditare da quella classe. Kotlin fornisce inoltre un meccanismo simile per proprietà, denominate proprietà di estensione.
val String.upperCaseFirstLetter: String get() = this.substring(0, 1).toUpperCase().plus(this.substring(1))
Nel post funzioni avanzate abbiamo definito una funzione di estensione uppercaseFirstLetter()
con tipo di ricevitore stringa.
A questo punto, l'abbiamo convertita in una proprietà di estensione di livello superiore. Si noti che è necessario definire un metodo getter sulla proprietà per farlo funzionare.
Quindi, con questa nuova conoscenza delle proprietà di estensione, saprai che se hai mai desiderato che una classe avesse una proprietà che non fosse disponibile, puoi creare una proprietà di estensione di quella classe.
4. Classi di dati
Cominciamo con una tipica classe Java o POJO (Plain Old Java Object).
public class BlogPost { private final String title; private final URI url; private final String description; private final Date publishDate; //.. constructor not included for brevity's sake @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BlogPost blogPost = (BlogPost) o; if (title != null ? !title.equals(blogPost.title) : blogPost.title != null) return false; if (url != null ? !url.equals(blogPost.url) : blogPost.url != null) return false; if (description != null ? !description.equals(blogPost.description) : blogPost.description != null) return false; return publishDate != null ? publishDate.equals(blogPost.publishDate) : blogPost.publishDate == null; } @Override public int hashCode() { int result = title != null ? title.hashCode() : 0; result = 31 * result + (url != null ? url.hashCode() : 0); result = 31 * result + (description != null ? description.hashCode() : 0); result = 31 * result + (publishDate != null ? publishDate.hashCode() : 0); return result; } @Override public String toString() { return "BlogPost{" + "title='" + title + '\'' + ", url=" + url + ", description='" + description + '\'' + ", publishDate=" + publishDate + '}'; } //.. setters and getters also ignored for brevity's sake }
Come potete vedere, dobbiamo codificare in modo esplicito gli accessori delle proprietà di classe: i metodi getter e setter, nonché i metodi hashcode,
uguali
e toString
(anche se IntelliJ IDEA, Android Studio o la libreria di AutoValue possono aiutarci a generarli). Vediamo questo tipo di codice di boilerplate principalmente nello strato di dati di un tipico progetto Java. (Ho rimosso gli accessoristi del campo e il costruttore per motivi di brevità).
La cosa cool è che il team di Kotlin ci ha fornito il modifier dei dati
per le classi per eliminare la scrittura di questi boilerplate.
Ora scriviamo il codice precedente in Kotlin.
data class BlogPost(var title: String, var url: URI, var description: String, var publishDate: Date)
Eccezionale! Basta specificare il modificatore di dati
prima della parola chiave di classe
per creare una classe di dati, proprio come quello che abbiamo fatto nella nostra classe di BlogPost
Kotlin sopra. Ora i metodi equal
, hashcode
, toString
, copia
e più componenti verranno creati sotto il cofano per noi. Si noti che una classe di dati può estendere altre classi (questa è una nuova caratteristica di Kotlin 1.1).
Il metodo uguale
Questo metodo confronta due oggetti per uguaglianza e restituisce true se sono altrimenti uguali o falsi. In altre parole, confronta se le due istanze di classe contengono gli stessi dati.
student.equals(student3) // using the == in Kotlin student == student3 // same as using equals()
In Kotlin, utilizzando l'operatore di uguaglianza ==
chiamerà il metodo uguale
dietro le quinte.
Il metodo hashCode
Questo metodo restituisce un valore intero utilizzato per la memorizzazione rapida e il recupero di dati memorizzati in una struttura di dati di raccolta basata su hash, ad esempio nei tipi di raccolta HashMap
e HashSet.
Il metodo toString
Questo metodo restituisce una rappresentazione String
di un oggetto.
data class Person(var firstName: String, var lastName: String) val person = Person("Chike", "Mgbemena") println(person) // prints "Person(firstName=Chike, lastName=Mgbemena)"
Semplicemente chiamando l'istanza di classe, otterremo un oggetto di stringa restituito a noi - Kotlin chiama l'oggetto toString ()
sotto il cofano per noi. Ma se non mettiamo la parola chiave dei dati
, vediamo quale sarà la rappresentazione della nostra stringa di oggetti:
com.chike.kotlin.classes.Person@2f0e140b
Molto meno informativo!
Metodo di copia
Questo metodo ci consente di creare una nuova istanza di un oggetto con tutti gli stessi valori di proprietà. In altre parole, crea una copia dell'oggetto.
val person1 = Person("Chike", "Mgbemena") println(person1) // Person(firstName=Chike, lastName=Mgbemena) val person2 = person1.copy() println(person2) // Person(firstName=Chike, lastName=Mgbemena)
Una cosa cool circa il metodo di copia
in Kotlin è la capacità di cambiare proprietà durante la copia.
val person3 = person1.copy(lastName = "Onu") println(person3) //Person3(firstName=Chike, lastName=Onu)
Se sei un codificatore Java, questo metodo è simile al metodo clone ()
già conosciuto. Ma il metodo di copia
Kotlin ha funzionalità più potenti.
Dichiarazione distruttiva
Nella classe Person
, abbiamo anche due metodi generati automaticamente da noi dal compilatore a causa della parola chiave dei dati
inserita nella classe. Questi due metodi sono prefisso con "componente", seguito da un suffisso numerico: component1 ()
, component2 ().
Ognuno di questi metodi rappresenta le singole proprietà del tipo. Si noti che il suffisso corrisponde all'ordine delle proprietà dichiarate nel costruttore primario.
Quindi, nel nostro esempio chiamando component1 ()
restituirà il primo nome e chiamando component2 ()
restituirà il cognome.
println(person3.component1()) // Chike println(person3.component2()) // Onu
Chiamare le proprietà utilizzando questo stile è difficile da capire e leggere, però, chiamando esplicitamente il nome della proprietà è molto meglio. Tuttavia, queste proprietà implicitamente create hanno uno scopo molto utile: ci fanno una dichiarazione di deformazione, in cui possiamo assegnare ciascun componente a una variabile locale.
val (firstName, lastName) = Person("Angelina", "Jolie") println(firstName + " " + lastName) // Angelina Jolie
Quello che abbiamo fatto qui è quello di assegnare rispettivamente le prime e le seconde proprietà (firstName
e lastName
) del tipo Persona
alle variabili firstName
e lastName
rispettivamente. Ho anche discusso questo meccanismo noto come dichiarazione di destructuring nell'ultima sezione del pacchetto e Funzioni di base post.
5. Nidificate classi
Nella posta più divertente con funzioni, ti ho detto che Kotlin supporta funzioni locali o nidificate, una funzione che viene dichiarata all'interno di un'altra funzione. Beh, Kotlin supporta anche le classi nidificate, una classe creata all'interno di un'altra classe.
class OuterClass { class NestedClass { fun nestedClassFunc() { } } }
Chiamiamo anche le funzioni pubbliche della classe nidificata come di seguito riportato: una classe nidificata in Kotlin equivale a una classe nidificata statica
in Java. Si noti che le classi nidificate non possono memorizzare un riferimento alla loro classe esterna.
val nestedClass = OuterClass.NestedClass() nestedClass.nestedClassFunc()
Siamo anche liberi di impostare la classe nidificata come privata. Ciò significa che possiamo solo creare un'istanza del NestedClass
nell'ambito dell'OuterClass.
Classe interna
Le classi interne, d'altra parte, possono fare riferimento la classe esterna che è stata dichiarata nel. Per creare una classe interna,
abbiamo posto la parola chiave interna prima della parola chiave class
in una classe nidificata.
class OuterClass() { val oCPropt: String = "Yo" inner class InnerClass { fun innerClassFunc() { val outerClass = this@OuterClass print(outerClass.oCPropt) // prints "Yo" } } }
Qui si fa riferimento la OuterClass
dalla InnerClass
utilizzando this@OuterClass.
6. Enumera classi
Un tipo enum dichiara un insieme di costanti rappresentato da identificatori. Questo speciale tipo di classe è creato dalla parola chiave enum
specificato prima la parola chiave class.
enum class Country { NIGERIA, GHANA, CANADA }
Per recuperare un valore enum basato sul suo nome (proprio come in Java), facciamo questo:
Country.valueOf("NIGERIA")
O possiamo usare il enumValueOf Kotlin<T>()
metodo di supporto per le costanti di accesso in modo generico:</T>
enumValueOf<Country>("NIGERIA")
Inoltre, possiamo ottenere tutti i valori (come per un enum Java) come questo:
Country.values()
Infine, possiamo usare il enumValues Kotlin<T>()
metodo di supporto per ottenere tutte le voci di enum in modo generico:</T>
enumValues<Country>()
Restituisce una matrice contenente le voci di enum.
Costruttori di enum
Proprio come una normale classe, il tipo enum
può avere un proprio costruttore con proprietà associate a ciascuna costante di enum.
enum class Country(val callingCode: Int) { NIGERIA (234), USA (1), GHANA (233) }
Nel costruttore
paese enum tipo primario, abbiamo definito il callingCodes
di proprietà non modificabili per ogni costante di enum. In ciascuna delle costanti, abbiamo passato un argomento al costruttore.
Possiamo quindi accedere la proprietà costanti come questo:
val country = Country.NIGERIA print(country.callingCode) // 234
7. Classi sealed
Una classe sealed in Kotlin è una classe astratta (non si prevede di creare oggetti da esso), che possono estendere altre classi. Queste sottoclassi sono definite all'interno del corpo di classe sealed — nello stesso file. Perché tutte queste sottoclassi sono definite all'interno del corpo di classe sealed, possiamo sapere tutte le sottoclassi possibile semplicemente visualizzando il file.
Vediamo un esempio pratico.
// shape.kt sealed class Shape class Circle : Shape() class Triangle : Shape() class Rectangle: Shape()
Per dichiarare una classe come sealed
, inseriamo il modificatore sealed prima il modificatore di classe
nell'intestazione della dichiarazione di classe — nel nostro caso, abbiamo dichiarato la classe Shape
come sealed
. Una classe sealed è incompleta senza sue sottoclassi — proprio come una tipica classe astratta — quindi dobbiamo dichiarare le sottoclassi individuali all'interno del file stesso (shape.kt in questo caso). Si noti che non è possibile definire una sottoclasse di una classe sealed da un altro file.
Nel nostro codice di cui sopra, abbiamo specificato che la classe Shape
può essere estesa solo dalle classi rettangolo,
triangolo
e cerchio.
Le classi sealed in Kotlin hanno le seguenti regole aggiuntive:
- Possiamo aggiungere l'
abstract
di modificatore per una classe sealed, ma questo è ridondante perché le classi sealed sono astratte per impostazione predefinita. - Le classi sealed non possono avere il modificatore
aperto
ofinale
. - Siamo anche liberi di dichiarare classi di dati e oggetti come le sottoclassi di una classe sealed (hanno ancora bisogno di essere dichiarato nello stesso file).
- Le classi sealed non possono avere costruttori pubblici — loro costruttori sono privati per impostazione predefinita.
Le classi che estendono le sottoclassi di una classe sealed possono essere posizionate nel file stesso o un altro file. La sottoclasse di classe sealed deve essere contrassegnato con il modificatore aperto
(scoprirai ulteriori informazioni sull'ereditarietà in Kotlin nel prossimo post).
// employee.kt sealed class Employee open class Artist : Employee() // musician.kt class Musician : Artist()
Una classe sealed e relative sottoclassi sono molto utili in una quando
espressione. Per esempio:
fun whatIsIt(shape: Shape) = when (shape) { is Circle -> println("A circle") is Triangle -> println("A triangle") is Rectangle -> println("A rectangle") }
Qui il compilatore è intelligente per garantire abbiamo coperto quando
tutti i possibili casi. Ciò significa che non c'è nessuna necessità
di aggiungere la clausola else.
Se dovessimo fare invece la seguente:
fun whatIsIt(shape: Shape) = when (shape) { is Circle -> println("A circle") is Triangle -> println("A triangle") }
Il codice non compilato, perché non abbiamo incluso tutti i casi possibili. Avremmo il seguente errore:
Kotlin: 'when' expression must be exhaustive, add necessary 'is Rectangle' branch or 'else' branch instead.
Così abbiamo potuto includere il caso rettangolo
o includere la clausola
else per completare il quando
espressione.
Conclusione
In questo tutorial, imparato di più su classi in Kotlin. Abbiamo coperto le seguenti informazioni sulle proprietà di classe:
- inizializzazione di tardo
- Proprietà inline
- Proprietà di estensione
Inoltre, hai imparato alcune classi cool e avanzate quali dati, enum, annidati e classi sealed. Nel prossimo tutorial della serie Kotlin da zero, verrà introdotto per interfacce ed ereditarietà in Kotlin. A presto!
Per ulteriori informazioni sul linguaggio Kotlin, mi consiglia di visitare la documentazione di Kotlin. O Scopri alcuni dei nostri altri Android app sviluppo post qui su Envato Tuts!
- Android SDKConcorrenza in RxJava 2Chike Mgbemena
- Android SDKL'invio di dati con Retrofit 2 HTTP Client per AndroidChike Mgbemena
- Android SDKJava vs Kotlin: dovrebbe voi usare Kotlin per lo sviluppo Android?Jessica Thornsby
- Android SDKCome creare un'App Android Chat utilizzando FirebaseAshraff Hathibelagal
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post