Impara il linguaggio di programmazione Kotlin

Kotlin è un linguaggio di programmazione ampiamente usato dagli sviluppatori Android di tutto il mondo. Questo argomento funge da corso crollato di Kotlin per aiutarti a iniziare rapidamente.

Dichiarazione della variabile

Kotlin utilizza due parole chiave diverse per dichiarare le variabili: val e var.

  • Utilizza val per una variabile il cui valore non cambia mai. Non puoi riassegnare un valore a una variabile dichiarata utilizzando val.
  • Utilizza var per una variabile il cui valore può cambiare.

Nell'esempio riportato di seguito, count è una variabile di tipo Int a cui viene assegnato un valore iniziale di 10:

var count: Int = 10

Int è un tipo che rappresenta un numero intero, uno dei molti tipi numerici che possono essere rappresentati in Kotlin. Come per altre lingue, puoi anche utilizzare Byte, Short, Long, Float e Double a seconda dei tuoi dati numerici.

La parola chiave var indica che puoi riassegnare i valori a count in base alle esigenze. Ad esempio, puoi modificare il valore di count da 10 a 15:

var count: Int = 10
count = 15

Tuttavia, alcuni valori non sono pensati per essere modificati. Prendi in considerazione un String chiamato languageName. Se vuoi assicurarti che languageName contenga sempre un valore di "Kotlin", puoi dichiarare languageName utilizzando la parola chiave val:

val languageName: String = "Kotlin"

Queste parole chiave ti consentono di indicare esplicitamente cosa può essere modificato. Usali a tuo vantaggio in base alle necessità. Se un riferimento a una variabile deve essere riassegnabile, dichiaralo come var. In caso contrario, usa val.

Inferenza del tipo

Continuando con l'esempio precedente, quando assegni un valore iniziale a languageName, il compilatore Kotlin può dedurre il tipo in base al tipo del valore assegnato.

Poiché il valore di "Kotlin" è del tipo String, il compilatore deduce che languageName è anche un String. Tieni presente che Kotlin è un linguaggio di tipo statico. Ciò significa che il tipo viene risolto al momento della compilazione e non cambia mai.

Nell'esempio seguente, languageName viene dedotto come String, quindi non puoi chiamare alcuna funzione che non faccia parte della classe String:

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() è una funzione che può essere chiamata solo per le variabili di tipo String. Poiché il compilatore Kotlin ha dedotto languageName come String, puoi chiamare toUpperCase() in sicurezza. inc(), tuttavia, è una funzione operatore Int, quindi non può essere richiamata su un String. L'approccio di Kotlin all'inferenza tipo offre concisione e sicurezza del tipo.

Sicurezza nulla

In alcuni linguaggi, una variabile di tipo di riferimento può essere dichiarata senza fornire un valore esplicito iniziale. In questi casi, le variabili di solito contengono un valore nullo. Le variabili Kotlin non possono contenere valori null per impostazione predefinita. Ciò significa che il seguente snippet non è valido:

// Fails to compile
val languageName: String = null

Affinché una variabile contenga un valore null, deve essere di tipo nullable. Puoi specificare una variabile come null sufficiente eseguendo il suffisso del tipo con ?, come mostrato nell'esempio seguente:

val languageName: String? = null

Con un tipo String?, puoi assegnare un valore String o null a languageName.

Devi gestire attentamente le variabili con null per non rischiare di incorrere in una temuta NullPointerException. In Java, ad esempio, se tenti di richiamare un metodo su un valore nullo, il programma si arresta in modo anomalo.

Kotlin fornisce una serie di meccanismi per lavorare in sicurezza con le variabili con null. Per ulteriori informazioni, consulta la sezione Pattern Kotlin comuni in Android: nullabilità.

Condizionali

Kotlin presenta diversi meccanismi per implementare la logica condizionale. La più comune è l'istruzione if-else. Se un'espressione racchiusa tra parentesi accanto a una parola chiave if restituisce true, viene eseguito il codice all'interno di quel ramo (ovvero il codice immediatamente successivo racchiuso tra parentesi graffe). In caso contrario, viene eseguito il codice all'interno del ramo else.

if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

Puoi rappresentare più condizioni utilizzando else if. Ciò ti consente di rappresentare una logica più granulare e complessa all'interno di una singola istruzione condizionale, come mostrato nell'esempio seguente:

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

Le istruzioni condizionali sono utili per rappresentare la logica stateful, ma potresti scoprire di ripeterle quando le scrivi. Nell'esempio precedente, è sufficiente stampare un String in ogni ramo. Per evitare questa ripetizione, Kotlin offre espressioni condizionali. L'ultimo esempio può essere riscritto come segue:

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

In modo implicito, ogni ramo condizionale restituisce il risultato dell'espressione nella riga finale, pertanto non è necessario utilizzare una parola chiave return. Poiché il risultato di tutti e tre i rami è di tipo String, anche il risultato dell'espressione if-else è di tipo String. In questo esempio, a answerString viene assegnato un valore iniziale a partire dal risultato dell'espressione if-else. L'inferenza del tipo può essere utilizzata per omettere la dichiarazione esplicita del tipo per answerString, ma spesso è una buona idea includerla per maggiore chiarezza.

Man mano che la complessità dell'istruzione condizionale aumenta, ti consigliamo di sostituire l'espressione if-else con un'espressione when, come mostrato nell'esempio seguente:

val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

Ogni ramo in un'espressione when è rappresentato da una condizione, una freccia (->) e un risultato. Se la condizione sul lato sinistro della freccia restituisce true, il risultato dell'espressione sul lato destro viene restituito. Tieni presente che l'esecuzione non avviene da un ramo all'altro. Il codice nell'esempio dell'espressione when è funzionalmente equivalente a quello dell'esempio precedente, ma è probabilmente più facile da leggere.

I condizionali di Kotlin mettono in evidenza una delle sue funzionalità più potenti, la trasmissione intelligente. Anziché utilizzare l'operatore di chiamata sicura o di assertion "not-null" per lavorare con valori nulli, puoi verificare se una variabile contiene un riferimento a un valore nullo utilizzando un'istruzione condizionale, come mostrato nell'esempio seguente:

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

All'interno del ramo condizionale, languageName può essere considerato un valore non null. Kotlin è abbastanza intelligente da riconoscere che la condizione per l'esecuzione del ramo è che languageName non contiene un valore nullo, quindi non devi considerare languageName come null all'interno di quel ramo. Questa trasmissione intelligente funziona per controlli di null, controlli del tipo o qualsiasi condizione che soddisfi un contratto.

Funzioni

Puoi raggruppare una o più espressioni in una funzione. Invece di ripetere la stessa serie di espressioni ogni volta che ti serve un risultato, puoi aggregare le espressioni in una funzione e chiamare quella funzione.

Per dichiarare una funzione, utilizza la parola chiave fun seguita dal nome della funzione. Quindi, definisci i tipi di input presi dalla funzione, se presenti, e dichiara il tipo di output che restituisce. Nel corpo di una funzione definisci le espressioni che vengono richiamate.

Partendo dagli esempi precedenti, ecco una funzione di Kotlin completa:

fun generateAnswerString(): String {
    val answerString = if (count == 42) {
        "I have the answer."
    } else {
        "The answer eludes me"
    }

    return answerString
}

La funzione nell'esempio precedente ha il nome generateAnswerString. non richiede input. Restituisce un risultato di tipo String. Per chiamare una funzione, utilizza il nome corrispondente, seguito dall'operatore di chiamata (()). Nell'esempio seguente, la variabile answerString viene inizializzata con il risultato di generateAnswerString().

val answerString = generateAnswerString()

Le funzioni possono utilizzare gli argomenti come input, come mostrato nell'esempio seguente:

fun generateAnswerString(countThreshold: Int): String {
    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }

    return answerString
}

Quando dichiari una funzione, puoi specificare un numero qualsiasi di argomenti e i relativi tipi. Nell'esempio precedente, generateAnswerString() accetta un argomento denominato countThreshold di tipo Int. All'interno della funzione, puoi fare riferimento all'argomento usandone il nome.

Quando chiami questa funzione, devi includere un argomento tra le parentesi della chiamata di funzione:

val answerString = generateAnswerString(42)

Semplificare le dichiarazioni di funzioni

generateAnswerString() è una funzione abbastanza semplice. La funzione dichiara una variabile e poi restituisce immediatamente. Quando il risultato di una singola espressione viene restituito da una funzione, puoi saltare la dichiarazione di una variabile locale restituendo direttamente il risultato dell'espressione if-else contenuta nella funzione, come mostrato nell'esempio seguente:

fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }
}

Puoi anche sostituire la parola chiave restituita con l'operatore di assegnazione:

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
        "I have the answer"
    } else {
        "The answer eludes me"
    }

Funzioni anonime

Non tutte le funzioni hanno bisogno di un nome. Alcune funzioni sono identificate più direttamente dai rispettivi input e output. Queste funzioni sono chiamate funzioni anonime. Puoi mantenere un riferimento a una funzione anonima, utilizzando questo riferimento per chiamare la funzione anonima in un secondo momento. Puoi anche trasferire il riferimento all'applicazione, come per altri tipi di riferimento.

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

Come le funzioni con nome, le funzioni anonime possono contenere un numero qualsiasi di espressioni. Il valore restituito della funzione è il risultato dell'espressione finale.

Nell'esempio precedente, stringLengthFunc contiene un riferimento a una funzione anonima che prende un String come input e restituisce la lunghezza dell'input String come output di tipo Int. Per questo motivo, il tipo di funzione è indicato come (String) -> Int. Questo codice, tuttavia, non richiama la funzione. Per recuperare il risultato della funzione, devi richiamarla utilizzando una funzione con nome. Devi fornire un String durante la chiamata a stringLengthFunc, come mostrato nell'esempio seguente:

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

val stringLength: Int = stringLengthFunc("Android")

Funzioni di ordine superiore

Una funzione può assumere come argomento un'altra funzione. Le funzioni che utilizzano altre funzioni come argomenti sono chiamate funzioni di ordine superiore. Questo pattern è utile per comunicare tra i componenti nello stesso modo in cui si utilizza un'interfaccia di callback in Java.

Ecco un esempio di funzione di ordine superiore:

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

La funzione stringMapper() prende un String insieme a una funzione che ricava un valore Int da un String che passi al suo interno.

Puoi chiamare stringMapper() passando un String e una funzione che soddisfi l'altro parametro di input, ovvero una funzione che accetta un String come input e restituisce un Int, come mostrato nell'esempio seguente:

stringMapper("Android", { input ->
    input.length
})

Se la funzione anonima è l'ultimo parametro definito in una funzione, puoi passarla al di fuori delle parentesi utilizzate per richiamare la funzione, come mostrato nell'esempio seguente:

stringMapper("Android") { input ->
    input.length
}

Le funzioni anonime sono disponibili nell'intera libreria standard Kotlin. Per ulteriori informazioni, consulta Funzioni di ordine superiore e lambda.

Classi

Tutti i tipi menzionati finora sono incorporati nel linguaggio di programmazione Kotlin. Se vuoi aggiungere un tipo personalizzato, puoi definire una classe utilizzando la parola chiave class, come illustrato nell'esempio seguente:

class Car

Proprietà

Le classi rappresentano lo stato che utilizza le proprietà. Una proprietà è una variabile a livello di classe che può includere un getter, un setter e un campo di supporto. Poiché un'auto ha bisogno delle ruote per poter guidare, puoi aggiungere un elenco di oggetti Wheel come proprietà di Car, come mostrato nell'esempio seguente:

class Car {
    val wheels = listOf<Wheel>()
}

Tieni presente che wheels è un public val, il che significa che wheels è accessibile dall'esterno della classe Car e non può essere riassegnato. Se vuoi ottenere un'istanza di Car, devi prima chiamare il relativo costruttore. Da qui puoi accedere a tutte le sue proprietà accessibili.

val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

Se vuoi personalizzare le ruote, puoi definire un costruttore personalizzato che specifichi come vengono inizializzate le proprietà della classe:

class Car(val wheels: List<Wheel>)

Nell'esempio precedente, il costruttore della classe prende un List<Wheel> come argomento del costruttore e lo utilizza per inizializzare la relativa proprietà wheels.

Funzioni di classe e incapsulamento

Le classi usano le funzioni per modellare il comportamento. Le funzioni possono modificare lo stato, consentendo di esporre solo i dati che vuoi esporre. Questo controllo dell'accesso fa parte di un concetto più ampio orientato agli oggetti noto come incapsulamento.

Nell'esempio seguente, la proprietà doorLock viene mantenuta privata da qualsiasi elemento al di fuori della classe Car. Per aprire l'auto, devi chiamare la funzione unlockDoor() che trasmette una chiave valida, come mostrato nell'esempio seguente:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

Se vuoi personalizzare la modalità di riferimento a una proprietà, puoi fornire un getter e un setter personalizzati. Ad esempio, se vuoi esporre il getter di una proprietà limitando al contempo l'accesso al relativo setter, puoi designare questo setter come private:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

Con una combinazione di proprietà e funzioni, puoi creare classi che modellano tutti i tipi di oggetti.

Interoperabilità

Una delle funzionalità più importanti di Kotlin è la sua interoperabilità fluida con Java. Poiché il codice Kotlin viene compilato in bytecode JVM, il tuo codice Kotlin può chiamare direttamente nel codice Java e viceversa. Ciò significa che puoi sfruttare le librerie Java esistenti direttamente da Kotlin. Inoltre, la maggior parte delle API Android sono scritte in Java e puoi chiamarle direttamente da Kotlin.

Passaggi successivi

Kotlin è un linguaggio flessibile e pragmatico che registra un sostegno e uno slancio continui. Se ancora non l'hai fatto, ti invitiamo a farlo. Per i passaggi successivi, consulta la documentazione ufficiale di Kotlin e la guida su come applicare pattern Kotlin comuni nelle tue app per Android.