Bottcamp Kotlin per programmatori 5.1: estensioni

Questo codelab fa parte del Kotlin Bootcamp for Programs. Otterrai il massimo valore da questo corso se lavori in sequenza nei codelab. A seconda delle tue conoscenze, potresti essere in grado di scorrere alcune sezioni. Questo corso è rivolto ai programmatori che conoscono una lingua orientata agli oggetti e vogliono imparare il kotlin.

Introduzione

In questo codelab ti verranno presentate diverse funzionalità utili in Kotlin, tra cui coppie, raccolte e funzioni di estensione.

Anziché creare un'unica app di esempio, le lezioni di questo corso sono state concepite per sviluppare le tue conoscenze, ma sono semi-indipendenti gli uni dagli altri per esplorare le sezioni che conosci. Per collegare tutti questi esempi, molti sono basati su un acquario. E se vuoi vedere l'intera storia dell'acquario, dai un'occhiata al Bootcamp Kotlin per programmatoriCorso Udacity.

Informazioni importanti

  • La sintassi delle funzioni, delle classi e dei metodi di Kotlin
  • Come utilizzare REPL (Read-Eval-Print Loop) di IntelliJ IDEA di Kotlin
  • Come creare una nuova classe in IntelliJ IDEA ed eseguire un programma

Obiettivi didattici

  • Come utilizzare coppie e triple
  • Scopri di più sulle collezioni
  • Definizione e utilizzo di costanti
  • Scrittura delle funzioni delle estensioni

In questo lab proverai a:

  • Scopri di più su coppie, triple e mappe hash in REPL
  • Scopri i diversi modi di organizzare le costanti
  • Scrivere una funzione estensione e una proprietà estensione

In questa attività imparerai a coppie e triple e a distruggerle. Le coppie e le triple sono classi di dati predefinite per 2 o 3 articoli generici. Questo può essere utile, ad esempio, perché una funzione restituisca più di un valore.

Supponiamo di avere un List di pesce e una funzione isFreshWater() per verificare se si trattava di un pesce d'acqua dolce o di acqua salata. List.partition() restituisce due elenchi, uno con gli elementi per i quali la condizione è pari a true e l'altro per gli elementi in cui la condizione è pari a false.

val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")

Passaggio 1: crea un paio di coppie e un triplo

  1. Apri il REPL (Strumenti > Kotlin > Kotlin REPL).
  2. Crea una coppia, associando un'apparecchiatura all'utilizzo, quindi stampa i valori. Puoi creare una coppia creando un'espressione che colleghi due valori, ad esempio due stringhe, con la parola chiave to, quindi utilizza .first o .second per fare riferimento a ciascun valore.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
  1. Crea una tripla e stampala con toString(), quindi convertila in un elenco con toList(). Puoi creare una tripla utilizzando Triple() con 3 valori. Utilizza .first, .second e .third per fare riferimento a ciascun valore.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42)
[6, 9, 42]

Gli esempi precedenti utilizzano lo stesso tipo per tutte le parti della coppia o della tripla, ma ciò non è obbligatorio. Le parti possono essere una stringa, un numero o un elenco, ad esempio un'altra coppia o tripla.

  1. Crea una coppia in cui la prima parte della coppia stessa è una coppia.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment
⇒ catching fish

Passaggio 2: elimina alcune coppie e tre triple

La separazione di coppie e triple nelle parti viene chiamata distruttività. Assegna la coppia o la tripla al numero corretto di variabili e Kotlin assegnerà il valore di ogni parte in ordine.

  1. Elimina una coppia e stampa i valori.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
  1. Elimina la tripla e stampa i valori.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42

Tieni presente che la strutturazione di coppie e triple funziona come per le classi di dati, trattate in un codelab precedente.

In questa attività troverai ulteriori informazioni sulle raccolte, tra cui gli elenchi e un nuovo tipo di raccolta, le mappe di hash.

Passaggio 1: scopri di più sugli elenchi

  1. Gli elenchi e gli elenchi modificabili sono stati introdotti in una lezione precedente. Sono una struttura di dati molto utili, quindi Kotlin fornisce una serie di funzioni integrate per gli elenchi. Esamina questo elenco parziale di funzioni per gli elenchi. Le schede complete sono disponibili nella documentazione di Kotlin per List e MutableList.

Funzione

Scopo

add(element: E)

Aggiungi un elemento all'elenco modificabile.

remove(element: E)

Rimuovi un elemento da un elenco modificabile.

reversed()

Restituisce una copia dell'elenco con gli elementi in ordine inverso.

contains(element: E)

Restituisci true se l'elenco contiene l'articolo.

subList(fromIndex: Int, toIndex: Int)

Restituisce una parte dell'elenco, dal primo indice fino a escludere il secondo.

  1. Ancora in esecuzione nel REPL, crea un elenco di numeri e chiama il numero sum(). In questo modo vengono sommati tutti gli elementi.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
  1. Crea un elenco di stringhe e somma l'elenco.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
  1. Se l'elemento non è in grado di sommare direttamente List, ad esempio una stringa, puoi specificare come sommarlo utilizzando .sumBy() con una funzione lambda, ad esempio con la lunghezza di ogni stringa. Il nome predefinito per un argomento lambda è it e qui it si riferisce a ogni elemento dell'elenco quando l'elenco viene barrato.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
  1. Con le liste puoi fare molto di più. Un modo per vedere la funzionalità disponibile è creare un elenco in IntelliJ IDEA, aggiungere il punto e poi controllare l'elenco di completamento automatico nella descrizione comando. Questa soluzione è valida per qualsiasi oggetto. Provalo con una lista.

  1. Scegli listIterator() dall'elenco, quindi seguilo con un'istruzione for e stampa tutti gli elementi separati da spazi.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
    println("$s ")
}
⇒ a bbb cc

Passaggio 2: prova le mappe hash

In Kotlin puoi mappare praticamente qualsiasi cosa con hashMapOf(). Le mappe hash sono come un elenco di coppie, in cui il primo valore funge da chiave.

  1. Crea una mappa hash che corrisponda ai sintomi, alle chiavi e alle malattie dei pesci, ovvero ai valori.
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  1. Puoi quindi recuperare il valore della malattia in base alla chiave del sintomo, utilizzando get() o parentesi quadre più brevi [].
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
  1. Prova a specificare un sintomo che non è presente sulla mappa.
println(cures["scale loss"])
⇒ null

Se nella mappa non è presente una chiave, il tentativo di restituire la malattia corrispondente restituisce null. In base ai dati della mappa, può capitare che non venga trovata una corrispondenza per una chiave possibile. In questi casi, Kotlin fornisce la funzione getOrDefault().

  1. Prova a cercare una chiave che non ha corrispondenze, usando getOrDefault().
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know

Se non devi solo restituire un valore, Kotlin fornisce la funzione getOrElse().

  1. Modifica il codice per utilizzare getOrElse() anziché getOrDefault().
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this

Anziché restituire un semplice valore predefinito, viene eseguito qualsiasi codice tra le parentesi graffe {}. Nell'esempio, else restituisce semplicemente una stringa, ma potrebbe essere interessante trovare una pagina web con una cura e restituirla.

Proprio come mutableListOf, puoi anche creare una mutableMapOf. Una mappa modificabile consente di inserire e rimuovere elementi. Muable significa cambiare, immutabile significa cambiare.

  1. Crea una mappa dell'inventario che può essere modificata, mappando una stringa dell'apparecchiatura al numero di elementi. Creala con una rete per pesci, quindi aggiungi 3 scrubber per serbatoi nell'inventario con put() e rimuovi la rete per i pesci con remove().
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}

In questa attività imparerai le costanti in Kotlin e i diversi modi di organizzarle.

Passaggio 1: scopri di più su cost e val

  1. Nel REPL, prova a creare una costante numerica. In Kotlin puoi creare costanti di primo livello e assegnarle un valore al momento della compilazione utilizzando const val.
const val rocks = 3

Il valore è assegnato e non può essere modificato, il che sembra molto simile a una dichiarazione val regolare. Quindi qual è la differenza tra const val e val? Il valore di const val è determinato al momento della compilazione, mentre il valore di val viene determinato durante l'esecuzione del programma. Ciò significa che val può essere assegnato a una funzione in fase di esecuzione.

Ciò significa che val può essere assegnato un valore da una funzione, ma const val non può farlo.

val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok

Inoltre, const val funziona solo al livello superiore e nelle classi singole dichiarate con object, non con le classi normali. Puoi utilizzarlo per creare un file o un oggetto singleton che contiene solo costanti e importarle in base alle necessità.

object Constants {
    const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2

Passaggio 2: crea un oggetto companion

Kotlin non ha un concetto di costanti a livello di classe.

Per definire le costanti all'interno di una classe, devi aggregarle agli oggetti companion dichiarati con la parola chiave companion. L'oggetto companion è sostanzialmente un oggetto singleton all'interno della classe.

  1. Crea una classe con un oggetto companion contenente una costante stringa.
class MyClass {
    companion object {
        const val CONSTANT3 = "constant in companion"
    }
}

La differenza di base tra gli oggetti companion e gli oggetti normali è:

  • Gli oggetti companion vengono inizializzati dal costruttore statico della classe che la contiene, ovvero quando vengono creati.
  • Gli oggetti standard vengono inizializzati tramite laziale al primo accesso a tale oggetto, ovvero quando vengono utilizzati per la prima volta.

ma c'è dell'altro, ma tutto ciò che devi sapere per il momento è racchiudere le costanti nelle classi di un oggetto companion.

In questa attività imparerai a estendere il comportamento dei corsi. Scrivere funzioni di utilità per estendere il comportamento di una classe è molto comune. Kotlin fornisce una comoda sintassi per dichiarare queste funzioni di utilità: le estensioni.

Le funzioni di estensione ti consentono di aggiungere funzioni a una classe esistente senza dover accedere al relativo codice sorgente. ad esempio in un file Extensions.kt che fa parte del pacchetto. Ciò non modifica effettivamente la classe, ma ti consente di utilizzare la annotazione del punto quando chiami la funzione sugli oggetti di tale classe.

Passaggio 1: scrivi una funzione dell'estensione

  1. Ancora in esecuzione nel REPL, scrivi una semplice funzione estensione, hasSpaces(), per verificare se una stringa contiene spazi. Il nome della funzione è preceduto dal prefisso della classe in cui opera. All'interno della funzione, this si riferisce all'oggetto su cui viene chiamata e it si riferisce all'iteratore nella chiamata find().
fun String.hasSpaces(): Boolean {
    val found = this.find { it == ' ' }
    return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
  1. Puoi semplificare la funzione hasSpaces(). Il thisnon è esplicitamente necessario e la funzione può essere ridotta a una singola espressione e restituita, quindi non sono più necessarie le parentesi graffe {} attorno ad esso.
fun String.hasSpaces() = find { it == ' ' } != null

Passaggio 2: scopri le limitazioni delle estensioni

Le funzioni di estensione hanno accesso solo all'API pubblica della classe che si estendono. Impossibile accedere alle variabili private.

  1. Prova ad aggiungere funzioni di estensione a una proprietà contrassegnata come private.
class AquariumPlant(val color: String, private val size: Int)

fun AquariumPlant.isRed() = color == "red"    // OK
fun AquariumPlant.isBig() = size > 50         // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
  1. Esamina il codice qui sotto e scopri che cosa verrà stampato.
open class AquariumPlant(val color: String, private val size: Int)

class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)

fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")

val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print()  // what will it print?
⇒ GreenLeafyPlant
AquariumPlant

plant.print() stampa GreenLeafyPlant. Anche aquariumPlant.print() potrebbe stampare GreenLeafyPlant, perché gli è stato assegnato il valore plant. Tuttavia, il tipo viene risolto al momento della compilazione, quindi AquariumPlant viene stampato.

Passaggio 3: aggiungi una proprietà estensione

Oltre alle funzioni delle estensioni, Kotlin ti consente anche di aggiungere proprietà delle estensioni. Come per le estensioni, puoi specificare la classe che stai estendendo, seguita da un punto e dal nome della proprietà.

  1. Ancora in fase di elaborazione nel REPL, aggiungi una proprietà dell'estensione isGreen a AquariumPlant, che è true se il colore è verde.
val AquariumPlant.isGreen: Boolean
   get() = color == "green"

È possibile accedere alla proprietà isGreen proprio come una proprietà normale; quando si accede, il getter per isGreen viene chiamato per ottenere il valore.

  1. Stampa la proprietà isGreen per la variabile aquariumPlant e osserva il risultato.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true

Passaggio 4: scopri i destinatari nulli

La classe che espandi si chiama destinatario ed è possibile rendere nulla tale classe. In tal caso, la variabile this utilizzata nel corpo può essere null, pertanto assicurati di testarla. Potresti prendere un ricevitore senza valore se prevedi che i chiamanti chiamino il tuo metodo di estensione per le variabili non visibili o se vuoi fornire un comportamento predefinito quando la funzione viene applicata a null.

  1. Ancora in fase di elaborazione nel REPL, definisci un metodo pull() che accetti un ricevitore nullable. Viene indicato con un punto interrogativo ? dopo il tipo, prima del punto. All'interno del corpo, puoi verificare se this non è null utilizzando il punto interrogativo-applica-?.apply.
fun AquariumPlant?.pull() {
   this?.apply {
       println("removing $this")
   }
}

val plant: AquariumPlant? = null
plant.pull()
  1. In questo caso non viene generato alcun output quando esegui il programma. Poiché plant è null, l'elemento println() interno non viene chiamato.

Le funzioni delle estensioni sono molto potenti e la maggior parte della libreria standard Kotlin viene implementata come funzioni delle estensioni.

In questa lezione hai imparato di più sulle collezioni, hai imparato sulle costanti e hai imparato la potenza delle funzioni e delle proprietà delle estensioni.

  • È possibile utilizzare coppie e triple per restituire più di un valore da una funzione. Ad esempio:
    val twoLists = fish.partition { isFreshWater(it) }
  • Kotlin ha molte funzioni utili per List, come reversed(), contains() e subList().
  • È possibile utilizzare un HashMap per mappare le chiavi ai valori. Ad esempio:
    val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  • Dichiara le costanti del tempo di compilazione utilizzando la parola chiave const. Puoi posizionarle al livello più alto, organizzarle in un oggetto singleton o posizionarle in un oggetto companion.
  • Un oggetto companion è un oggetto singleton all'interno di una definizione di classe, definito con la parola chiave companion.
  • Le proprietà e le funzioni delle estensioni possono aggiungere funzionalità a una classe. Ad esempio:
    fun String.hasSpaces() = find { it == ' ' } != null
  • Un destinatario nullo consente di creare estensioni in una classe che può essere null. L'operatore ?. può essere associato a apply per controllare null prima di eseguire il codice. Ad esempio:
    this?.apply { println("removing $this") }

Documentazione di Kotlin

Se vuoi saperne di più su qualsiasi argomento del corso o se ti blocchi, https://kotlinlang.org è il punto di partenza migliore.

Tutorial su Kotlin

Il sito web https://try.kotlinlang.org include tutorial avanzati chiamati Kotlin Koans, un interprete basato sul Web, e una serie completa di documentazione di riferimento con esempi.

Corso Udacity

Per visualizzare il corso Udacity su questo argomento, consulta il Kotlin Bootcamp for Programrs.

IDA IntelliJ

La documentazione relativa a IntelliJ IDEA è disponibile sul sito web di JetBrains.

In questa sezione sono elencati i possibili compiti per gli studenti che lavorano attraverso questo codelab nell'ambito di un corso tenuto da un insegnante. Spetta all'insegnante fare quanto segue:

  • Assegna i compiti, se necessario.
  • Comunica agli studenti come inviare compiti.
  • Valuta i compiti.

Gli insegnanti possono utilizzare i suggerimenti solo quanto e come vogliono e dovrebbero assegnare i compiti che ritengono appropriati.

Se stai lavorando da solo a questo codelab, puoi utilizzare questi compiti per mettere alla prova le tue conoscenze.

Rispondi a queste domande

Domanda 1

Quale dei seguenti elementi restituisce una copia di un elenco?

add()

remove()

reversed()

contains()

Domanda 2

Quale di queste funzioni di estensione su class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean) restituirà un errore di compilazione?

fun AquariumPlant.isRed() = color == "red"

fun AquariumPlant.isBig() = size > 45

fun AquariumPlant.isExpensive() = cost > 10.00

fun AquariumPlant.isNotLeafy() = leafy == false

Domanda 3

Quale dei seguenti non è un luogo in cui puoi definire costanti con const val?

▢ all'inizio del file

▢ nelle classi normali

▢ negli oggetti Singleton

▢ negli oggetti companion

Passa alla lezione successiva: 5.2 Generici

Per una panoramica del corso, inclusi i link ad altri codelab, vedi "Kotlin Bootcamp for Programs: Welcome to the Course."