Kotlin From Scratch proprietà e classi avanzate

Kotlin è un linguaggio di programmazione moderno che viene compilato in bytecode Java. È gratuito e open source e promette di rendere la codifica per Android ancora più divertente.  

Nell'articolo precedente, hai imparato a conoscere classi e oggetti in Kotlin. In questo tutorial, continueremo a saperne di più sulle proprietà e analizzeremo anche i tipi avanzati di classi in Kotlin esplorando quanto segue:

  • proprietà late-inizializzate
  • proprietà in linea 
  • proprietà di estensione
  • classi di dati, enum, nidificati e sigillati

1. Proprietà tardive inizializzate

Possiamo dichiarare una proprietà non nullo in Kotlin come tardo-inizializzato. Ciò significa che una proprietà non nullo non verrà inizializzata al momento della dichiarazione con un valore - l'effettiva inizializzazione non avverrà tramite alcun costruttore - ma, invece, verrà inizializzata in ritardo da un metodo o un'iniezione di dipendenza.

Diamo un'occhiata a un esempio per capire questo modificatore di proprietà unico. 

class Presenter repository private var: repository? = null fun initRepository (repository: repository): Unit this.repository = repo class Repository fun saveAmount (amount: Double)  

Nel codice sopra, abbiamo dichiarato nullable mutabile deposito proprietà che è di tipo deposito-all'interno della classe Presentatore-e abbiamo quindi inizializzato questa proprietà su null durante la dichiarazione. Abbiamo un metodo initRepository () nel Presentatore classe che reinizializza questa proprietà in seguito con un effettivo deposito esempio. Si noti che a questa proprietà può essere assegnato anche un valore utilizzando un injector di dipendenza come Dagger.     

Ora per noi invocare metodi o proprietà su questo deposito proprietà, dobbiamo fare un controllo nullo o utilizzare l'operatore di chiamata sicura. Perché? Perché il deposito la proprietà è di tipo nullable (deposito?). (Se hai bisogno di un aggiornamento sul nullability in Kotlin, visita gentilmente Nullability, Loops e Conditions).

// Inside Presenter fun salva divertenti (importo: Double) repository? .SaveAmount (amount)

Per evitare di dover eseguire controlli nulli ogni volta che è necessario richiamare il metodo di una proprietà, possiamo contrassegnare tale proprietà con lateinit modificatore-questo significa che abbiamo dichiarato tale proprietà (che è un'istanza di un'altra classe) come tardo-inizializzato (significa che la proprietà verrà inizializzata in seguito).  

class Presenter private lateinit var repository: Repository // ...

Ora, finché aspetteremo che alla proprietà sia stato assegnato un valore, possiamo accedere tranquillamente ai metodi della proprietà senza effettuare controlli nulli. L'inizializzazione della proprietà può avvenire in un metodo setter o tramite dipendenza. 

repository.saveAmount (quantità)

Notare che se proviamo ad accedere ai metodi della proprietà prima che sia stato inizializzato, otterremo un kotlin.UninitializedPropertyAccessException invece di un NullPointerException. In questo caso, il messaggio di eccezione sarà "il repository di proprietà lateinit non è stato inizializzato". 

Notare anche le seguenti restrizioni poste quando si ritardano l'inizializzazione di una proprietà con lateinit:

  • Deve essere mutabile (dichiarato con var).
  • Il tipo di proprietà non può essere un tipo primitivo, ad esempio, Int, Doppio, Galleggiante, e così via. 
  • La proprietà non può avere getter o setter personalizzati.

2. Proprietà in linea

In Advanced Functions, ho introdotto il in linea modificatore per funzioni di ordine superiore: questo aiuta a ottimizzare le funzioni di ordine superiore che accettano un lambda come parametro. 

In Kotlin, possiamo anche usare questo in linea modificatore sulle proprietà. L'utilizzo di questo modificatore ottimizzerà l'accesso alla proprietà.

Vediamo un esempio pratico. 

class Student val nickName: String get () println ("Nick name recuperato") restituisce "koloCoder" fun main (args: Array) val student = Student () print (student.nickName)

Nel codice sopra, abbiamo una proprietà normale, nickName, quello non ha il in linea modificatore. Se decompiliamo lo snippet di codice, usando il Mostra Kotlin Bytecode funzione (se sei in IntelliJ IDEA o Android Studio, usa Utensili > Kotlin > Mostra Kotlin Bytecode), vedremo il seguente codice Java:

public final class Student @NotNull public final String getNickName () String var1 = "Nick name recuperato"; System.out.println (Q1); ritorna "koloCoder";  public final class InlineFunctionKt public static final void main (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); Studente = nuovo studente (); String var2 = student.getNickName (); System.out.print (var2); 

Nel codice Java generato sopra (alcuni elementi del codice generato sono stati rimossi per brevità), puoi vedere che all'interno di principale() metodo che il compilatore ha creato a Alunno oggetto, chiamato il getNickName () metodo, quindi stampato il suo valore di ritorno.  

Specifichiamo ora la proprietà come in linea invece, e confronta il codice byte generato.

// ... inline val nickName: String // ... 

Inseriamo semplicemente il in linea modificatore prima del modificatore di variabile: var o val. Ecco il bytecode generato per questa proprietà inline:

// ... public static final void main (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); Studente = nuovo studente (); String var3 = "Nick name recuperato"; System.out.println (VAR3); String var2 = "koloCoder"; System.out.print (var2);  // ... 

Di nuovo un codice è stato rimosso, ma la cosa fondamentale da notare è la principale() metodo. Il compilatore ha copiato la proprietà ottenere() corpo della funzione 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 della proprietà. Ma, come discusso nelle funzioni inline post, avremmo un codice byte più grande di prima, quindi usatelo con cautela. 

Si noti inoltre che questo meccanismo funzionerà per le proprietà che non dispongono di un campo di supporto (ricordare, un campo di supporto è solo un campo che viene utilizzato dalle proprietà quando si desidera modificare o utilizzare tali dati di campo). 

3. Proprietà di estensione 

Nelle funzioni avanzate ho anche discusso delle funzioni di estensione, queste ci danno la possibilità di estendere una classe con nuove funzionalità senza dover ereditare da quella classe. Kotlin fornisce anche un meccanismo simile per le proprietà, chiamato proprietà di estensione

val String.upperCaseFirstLetter: String get () = this.substring (0, 1) .toUpperCase (). plus (this.substring (1))

Nel post delle funzioni avanzate abbiamo definito a uppercaseFirstLetter () funzione di estensione con tipo di ricevitore Stringa. Qui, invece, l'abbiamo convertito in una proprietà di estensione di livello superiore. Nota che devi definire un metodo getter sulla tua proprietà affinché funzioni. 

Quindi, con questa nuova conoscenza delle proprietà dell'estensione, saprai che se hai mai desiderato che una classe avesse una proprietà che non era disponibile, sei libero di creare una proprietà di estensione di quella classe. 

4. Classi di dati

Iniziamo con una tipica classe Java o POJO (Plain Old Java Object). 

public class BlogPost private final String title; url URI finale privato; descrizione String privata finale; privato finale Data publishDate; // ... costruttore non incluso per brevità @Override public boolean equals (Object o) if (this == o) return true; if (o == null || getClass ()! = o.getClass ()) restituisce 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; risultato = 31 * risultato + (url! = null? url.hashCode (): 0); risultato = 31 * risultato + (descrizione! = null? description.hashCode (): 0); risultato = 31 * risultato + (publishDate! = null? publishDate.hashCode (): 0); ritorno risultato;  @Override public String toString () return "BlogPost " + "title = '" + title +' \ "+", url = "+ url +", + description + "\" + ", publishDate =" + publishDate + '';  // ... setter e getter anche ignorati per brevità

Come puoi vedere, dobbiamo codificare esplicitamente gli accessor di proprietà della classe: getter e setter, nonché codice hashè uguale a, e accordare metodi (sebbene IntelliJ IDEA, Android Studio o la libreria AutoValue possano aiutarci a generarli). Vediamo questo tipo di codice boilerplate principalmente nel livello dati di un tipico progetto Java. (Ho rimosso gli accessori di campo e il costruttore per brevità). 

La cosa bella è che il team di Kotlin ci ha fornito il dati modificatore per le classi per eliminare la scrittura di questi caratteri.

Scriviamo ora il codice precedente in Kotlin.

classe dati BlogPost (var title: String, var url: URI, var descrizione: String, var publishDate: Date)

Eccezionale! Specifichiamo solo il dati modificatore prima del classe parola chiave per creare una classe di dati, proprio come quello che abbiamo fatto nella nostra Post sul blog Classe Kotlin sopra. Ora il è uguale acodice hashaccordarecopia, e per noi saranno creati sotto molteplici aspetti metodi multipli. Si noti che una classe di dati può estendere altre classi (questa è una nuova funzionalità di Kotlin 1.1). 

Il è uguale a Metodo

Questo metodo confronta due oggetti per l'uguaglianza e restituisce true se sono uguali o falsi altrimenti. In altre parole, confronta se le due istanze di classe contengono gli stessi dati. 

student.equals (student3) // utilizzando il == nello studente Kotlin == student3 // uguale all'utilizzo di equals ()

In Kotlin, usando l'operatore di uguaglianza == chiamerà il è uguale a metodo dietro le quinte.

Il codice hash Metodo 

Questo metodo restituisce un valore intero utilizzato per l'archiviazione veloce e il recupero dei dati memorizzati in una struttura di raccolta dati basata su hash, ad esempio nel HashMap e HashSet tipi di raccolta.  

Il accordare Metodo

Questo metodo restituisce a Stringa rappresentazione di un oggetto. 

data class Person (var firstName: String, var lastName: String) val person = Person ("Chike", "Mgbemena") println (person) // stampa "Person (firstName = Chike, lastName = Mgbemena)"

Chiamando semplicemente l'istanza della classe, otteniamo un oggetto stringa restituito a noi-Kotlin chiama l'oggetto accordare() sotto il cofano per noi. Ma se non mettiamo il dati parola chiave in, vedere quale sarebbe la nostra rappresentazione stringa di oggetti: 

com.chike.kotlin.classes.Person@2f0e140b

Molto meno informativo!

Il copia Metodo

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 interessante del copia il metodo in Kotlin è la possibilità di modificare le proprietà durante la copia. 

val person3 = person1.copy (lastName = "Onu") println (person3) // Person3 (firstName = Chike, lastName = Onu)

Se sei un programmatore Java, questo metodo è simile al clone() metodo con cui hai già familiarità. Ma il Kotlin copia il metodo ha caratteristiche più potenti. 

Dichiarazione distruttiva

Nel Persona classe, abbiamo anche due metodi generati automaticamente dal compilatore a causa del dati parola chiave inserita nella classe. Questi due metodi sono preceduti da "componente", seguito da un suffisso numerico: Component1 ()Component2 (). Ciascuno di questi metodi rappresenta le singole proprietà del tipo. Si noti che il suffisso corrisponde all'ordine delle proprietà dichiarate nel costruttore principale.

Quindi, nel nostro esempio di chiamata Component1 () restituirà il nome e la chiamata Component2 () restituirà il cognome.

println (person3.component1 ()) // Chike println (person3.component2 ()) // Onu

Chiamare le proprietà usando questo stile è difficile da capire e leggere, quindi chiamare il nome della proprietà in modo esplicito è molto meglio. Tuttavia, queste proprietà create in modo implicito hanno uno scopo molto utile: ci permettono di fare una dichiarazione di destrutturazione, 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 è assegnare direttamente la prima e la seconda proprietà (nome di battesimo e cognome) del Persona digita alle variabili nome di battesimo e cognome rispettivamente. Ho anche discusso di questo meccanismo noto come dichiarazione di destrutturazione nell'ultima sezione del post Pacchetti e funzioni di base. 

5. Classi annidate

Nel post More Fun With Functions, ti ho detto che Kotlin supporta funzioni locali o nidificate, una funzione dichiarata all'interno di un'altra funzione. Bene, anche Kotlin supporta le classi annidate, una classe creata all'interno di un'altra classe. 

class OuterClass class NestedClass fun nestedClassFunc () 

Chiamiamo anche le funzioni pubbliche della classe annidata come visto di seguito: una classe nidificata in Kotlin è equivalente a a statico classe nidificata 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, questo significa che possiamo solo creare un'istanza di NestedClass nell'ambito del OuterClass

Classe interiore

Le classi interne, d'altra parte, possono fare riferimento alla classe esterna in cui è stato dichiarato. Per creare una classe interiore, poniamo il interno parola chiave prima del classe parola chiave in una classe annidata. 

class OuterClass () val oCPropt: String = "Yo" inner class InnerClass fun innerClassFunc () val outerClass = this @ OuterClass print (outerClass.oCPropt) // stampa "Yo"

Qui facciamo riferimento al OuterClass dal classe interna usando  questo @ OuterClass.

6. Classi Enum

Un tipo enum dichiara un insieme di costanti rappresentate da identificatori. Questo tipo speciale di classe è creato dalla parola chiave enum che è specificato prima del classe parola chiave. 

enum class Paese NIGERIA, GHANA, CANADA

Per recuperare un valore enum basato sul suo nome (proprio come in Java), facciamo questo:

Country.valueOf ( "NIGERIA")

Oppure possiamo usare il Kotlin enumValueOf() metodo di supporto per accedere alle costanti in modo generico:

enumValueOf("NIGERIA")

Inoltre, possiamo ottenere tutti i valori (come per un enum Java) come questo:

Country.values ​​()

Finalmente, possiamo usare il Kotlin EnumValues() metodo di supporto per ottenere tutte le voci enum in modo generico:

EnumValues()

Questo restituisce una matrice contenente le voci enum.  

Enum costruttori

Proprio come una classe normale, il enum type può avere il proprio costruttore con proprietà associate a ciascuna costante enum. 

enum class Paese (val callingCode: Int) NIGERIA (234), USA (1), GHANA (233)

Nel Nazione Enum tipo costruttore primario, abbiamo definito la proprietà immutabile callingCodes per ogni costante enum. In ciascuna delle costanti, abbiamo passato un argomento al costruttore. 

Possiamo quindi accedere alla proprietà constants in questo modo:

val country = Country.NIGERIA print (country.callingCode) // 234

7. Classi sigillati

Una classe sigillata in Kotlin è una classe astratta (non hai mai intenzione di creare oggetti da essa) che altre classi possono estendere. Queste sottoclassi sono definite all'interno del corpo della classe sigillata, nello stesso file. Poiché tutte queste sottoclassi sono definite all'interno del corpo della classe sigillata, possiamo conoscere tutte le sottoclassi possibili semplicemente visualizzando il file. 

Vediamo un esempio pratico. 

// shape.kt sealed class Forma class Circle: Shape () class Triangolo: Shape () class Rectangle: Shape ()

Per dichiarare una classe come sigillata, inseriamo il sigillato modificatore prima del classe modificatore nell'intestazione della dichiarazione di classe: nel nostro caso, abbiamo dichiarato il Forma classe come sigillato. Una classe sigillata è incompleta senza le sue sottoclassi - proprio come una tipica classe astratta - quindi dobbiamo dichiarare le singole sottoclassi all'interno dello stesso file (shape.kt in questo caso). Si noti che non è possibile definire una sottoclasse di una classe sigillata da un altro file. 

Nel nostro codice sopra, abbiamo specificato che il Forma la classe può essere estesa solo dalle classi CerchioTriangolo, e Rettangolo.

Le classi sigillate in Kotlin hanno le seguenti regole aggiuntive:

  • Possiamo aggiungere il modificatore astratto in una classe sigillata, ma questo è ridondante perché le classi sigillate sono astratte per impostazione predefinita.
  • Le classi sigillate non possono avere il Aperto o finale modificatore. 
  • Siamo anche liberi di dichiarare classi di dati e oggetti come sottoclassi a una classe sigillata (devono ancora essere dichiarati nello stesso file). 
  • Le classi sigillate non possono avere costruttori pubblici, i loro costruttori sono privati ​​di default. 

Le classi che estendono sottoclassi di una classe sigillata possono essere inserite nello stesso file o in un altro file. La sottoclasse della classe sigillata deve essere contrassegnata con Aperto modificatore (imparerai di più sull'ereditarietà di Kotlin nel prossimo post). 

// employee.kt sealed class Employee open class Artista: Employee () // musician.kt class Musicista: Artist ()

Una classe sigillata e le sue sottoclassi sono davvero utili in a quando espressione. Per esempio:

fun whatIsIt (shape: Shape) = when (shape) is Circle -> println ("A circle") è Triangle -> println ("A triangle") è Rectangle -> println ("A rectangle")

Qui il compilatore è intelligente per garantire che abbiamo coperto tutto il possibile quando casi. Ciò significa che non è necessario aggiungere il altro clausola. 

Se dovessimo fare quanto segue invece:

fun whatIsIt (shape: Shape) = when (shape) is Circle -> println ("A circle") è Triangle -> println ("A triangle")

Il codice non verrà compilato, perché non abbiamo incluso tutti i casi possibili. Avremmo il seguente errore:

Kotlin: "quando" l'espressione deve essere esaustiva, aggiungi invece il ramo "è rettangolare" o "altro".

Quindi potremmo includere il è Rectangle caso o includere il altro clausola per completare il quando espressione. 

Conclusione

In questo tutorial, hai imparato di più sulle classi in Kotlin. Abbiamo trattato quanto segue sulle proprietà della classe:

  • inizializzazione tardiva
  • proprietà in linea 
  • proprietà di estensione

Inoltre, hai imparato alcune classi interessanti e avanzate come dati, enum, classi nidificate e sigillate. Nel prossimo tutorial della serie Kotlin From Scratch, verrai presentato alle interfacce e all'ereditarietà di Kotlin. A presto!

Per saperne di più sulla lingua di Kotlin, ti consiglio di visitare la documentazione di Kotlin. Oppure guarda alcuni dei nostri altri post di sviluppo di app per Android qui su Envato Tuts!