Kotlin Bootcamp for Programmers 5.1: Extensions

Questo codelab fa parte del corso Kotlin Bootcamp for Programmers. Per ottenere il massimo valore da questo corso, ti consigliamo di seguire le codelab in sequenza. A seconda delle tue conoscenze, potresti riuscire a leggere rapidamente alcune sezioni. Questo corso è rivolto ai programmatori che conoscono un linguaggio orientato agli oggetti e vogliono imparare Kotlin.

Introduzione

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

Anziché creare una singola app di esempio, le lezioni di questo corso sono progettate per sviluppare le tue conoscenze, ma sono semi-indipendenti l'una dall'altra, in modo che tu possa scorrere rapidamente le sezioni che conosci. Per collegarli, molti esempi utilizzano un tema acquatico. Se vuoi scoprire tutta la storia dell'acquario, dai un'occhiata al corso Kotlin Bootcamp for Programmers di Udacity.

Cosa devi già sapere

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

Obiettivi didattici

  • Come lavorare con coppie e triple
  • Scopri di più sulle raccolte
  • Definizione e utilizzo delle costanti
  • Scrivere funzioni di estensione

In questo lab proverai a:

  • Scopri di più su coppie, triple e hash map nel REPL
  • Scopri diversi modi per organizzare le costanti
  • Scrivere una funzione di estensione e una proprietà di estensione

In questa attività imparerai a conoscere le coppie e le triple e a destrutturarle. Le coppie e le terne sono classi di dati predefinite per 2 o 3 elementi generici. Ad esempio, può essere utile per fare in modo che una funzione restituisca più di un valore.

Supponiamo di avere un List di pesci e una funzione isFreshWater() per verificare se il pesce è d'acqua dolce o salata. List.partition() restituisce due elenchi: uno con gli elementi in cui la condizione è true e l'altro con gli elementi in cui la condizione è false.

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

Passaggio 1: crea alcune coppie e triple

  1. Apri REPL (Strumenti > Kotlin > Kotlin REPL).
  2. Crea una coppia, associando un'attrezzatura all'uso a cui è destinata, quindi stampa i valori. Puoi creare una coppia creando un'espressione che collega due valori, ad esempio due stringhe, con la parola chiave to, quindi utilizzando .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(), poi convertila in un elenco con toList(). Crea 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 del triplo, ma non è obbligatorio. Le parti possono essere una stringa, un numero o un elenco, ad esempio, o anche un'altra coppia o tripla.

  1. Crea una coppia in cui la prima parte della coppia è a sua volta 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: destruttura alcune coppie e triple

La separazione di coppie e triple nei loro componenti è chiamata destrutturazione. Assegna la coppia o la tripla al numero appropriato di variabili e Kotlin assegnerà il valore di ogni parte in ordine.

  1. Destruttura 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. Destruttura una 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 destrutturazione di coppie e triple funziona allo stesso modo delle classi di dati, argomento trattato in un codelab precedente.

In questa attività scoprirai di più sulle raccolte, tra cui gli elenchi, e su un nuovo tipo di raccolta, le hash map.

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 utile, quindi Kotlin fornisce una serie di funzioni integrate per le liste. Consulta questo elenco parziale di funzioni per gli elenchi. Puoi trovare elenchi completi nella documentazione di Kotlin per List e MutableList.

Funzione

Purpose

add(element: E)

Aggiungi un elemento all'elenco modificabile.

remove(element: E)

Rimuovere un elemento da un elenco modificabile.

reversed()

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

contains(element: E)

Restituisce true se l'elenco contiene l'elemento.

subList(fromIndex: Int, toIndex: Int)

Restituisce una parte dell'elenco, dal primo indice fino al secondo indice escluso.

  1. Mentre lavori ancora in REPL, crea un elenco di numeri e chiama sum(). che riassume tutti gli elementi.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
  1. Crea un elenco di stringhe e sommale.
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 è qualcosa che List sa sommare direttamente, ad esempio una stringa, puoi specificare come sommarlo utilizzando .sumBy() con una funzione lambda, ad esempio per sommare in base alla lunghezza di ogni stringa. Il nome predefinito di un argomento lambda è it e it si riferisce a ogni elemento dell'elenco durante l'attraversamento.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
  1. Puoi fare molto di più con gli elenchi. Un modo per visualizzare la funzionalità disponibile è creare un elenco in IntelliJ IDEA, aggiungere il punto e poi esaminare l'elenco del completamento automatico nel suggerimento. Questa operazione funziona per qualsiasi oggetto. Prova con un elenco.

  1. Scegli listIterator() dall'elenco, quindi scorri l'elenco 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 a qualsiasi altra cosa utilizzando hashMapOf(). Le mappe hash sono una sorta di elenco di coppie, in cui il primo valore funge da chiave.

  1. Crea una mappa hash che corrisponda a sintomi, chiavi e malattie dei pesci, i 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, in modo ancora più breve, le parentesi quadre [].
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
  1. Prova a specificare un sintomo che non è presente nella mappa.
println(cures["scale loss"])
⇒ null

Se una chiave non è nella mappa, il tentativo di restituire la malattia corrispondente restituisce null. A seconda dei dati della mappa, è normale che non ci sia corrispondenza per una possibile chiave. Per casi come questo, Kotlin fornisce la funzione getOrDefault().

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

Se devi fare di più che restituire un valore, Kotlin fornisce la funzione getOrElse().

  1. Modifica il codice in modo che utilizzi getOrElse() anziché getOrDefault().
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this

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

Come per mutableListOf, puoi anche creare un mutableMapOf. Una mappa modificabile ti consente di inserire e rimuovere elementi. Modificabile significa che può essere modificato, immutabile significa che non può essere modificato.

  1. Crea una mappa dell'inventario modificabile, mappando una stringa di attrezzature al numero di articoli. Crealo con una rete da pesca, poi aggiungi 3 spugne per acquari all'inventario con put() e rimuovi la rete da pesca 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à, scoprirai le costanti in Kotlin e i diversi modi per organizzarle.

Passaggio 1: scopri le differenze tra const e val

  1. Nel REPL, prova a creare una costante numerica. In Kotlin, puoi creare costanti di primo livello e assegnare loro un valore in fase di compilazione utilizzando const val.
const val rocks = 3

Il valore viene assegnato e non può essere modificato, il che assomiglia molto alla dichiarazione di una normale val. Qual è la differenza tra const val e val? Il valore di const val viene determinato in fase di compilazione, mentre il valore di val viene determinato durante l'esecuzione del programma, il che significa che val può essere assegnato da una funzione in fase di runtime.

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

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

Inoltre, const val funziona solo a livello principale e nelle classi singleton dichiarate con object, non con le classi normali. Puoi utilizzarlo per creare un file o un oggetto singleton che contenga 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 il concetto di costanti a livello di classe.

Per definire le costanti all'interno di una classe, devi racchiuderle in oggetti complementari dichiarati con la parola chiave companion. L'oggetto companion è fondamentalmente un oggetto singleton all'interno della classe.

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

La differenza fondamentale tra gli oggetti companion e gli oggetti normali è:

  • Gli oggetti companion vengono inizializzati dal costruttore statico della classe contenitore, ovvero vengono creati quando viene creato l'oggetto.
  • Gli oggetti regolari vengono inizializzati in modo differito al primo accesso all'oggetto, ovvero quando vengono utilizzati per la prima volta.

C'è altro, ma per ora ti basterà sapere che devi racchiudere le costanti nelle classi in un oggetto companion.

In questa attività imparerai a estendere il comportamento delle classi. È molto comune scrivere funzioni di utilità per estendere il comportamento di una classe. Kotlin fornisce una sintassi comoda per dichiarare queste funzioni di utilità: le funzioni di estensione.

Le funzioni di estensione ti consentono di aggiungere funzioni a una classe esistente senza dover accedere al relativo codice sorgente. Ad esempio, puoi dichiararli in un file Extensions.kt che fa parte del tuo pacchetto. In realtà, questa operazione non modifica la classe, ma ti consente di utilizzare la notazione con il punto quando chiami la funzione sugli oggetti di quella classe.

Passaggio 1: scrivi una funzione di estensione

  1. Mentre lavori ancora nella REPL, scrivi una semplice funzione di estensione, hasSpaces(), per verificare se una stringa contiene spazi. Il nome della funzione è preceduto dalla classe su cui opera. All'interno della funzione, this si riferisce all'oggetto su cui viene chiamata, mentre 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(). this non è necessario in modo esplicito e la funzione può essere ridotta a una singola espressione e restituita, quindi non sono necessarie nemmeno le parentesi graffe {}.
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 estendono. Le variabili private non sono accessibili.

  1. Prova ad aggiungere funzioni di estensione a una proprietà contrassegnata con 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 riportato di seguito e scopri 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() stampe GreenLeafyPlant. Potresti aspettarti che anche aquariumPlant.print() stampi GreenLeafyPlant, perché gli è stato assegnato il valore plant. Tuttavia, il tipo viene risolto in fase di compilazione, quindi viene stampato AquariumPlant.

Passaggio 3: aggiungi una proprietà di estensione

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

  1. Mentre lavori ancora in REPL, aggiungi una proprietà di estensione isGreen a AquariumPlant, che è true se il colore è verde.
val AquariumPlant.isGreen: Boolean
   get() = color == "green"

È possibile accedere alla proprietà isGreen come a una proprietà normale. Quando si accede, viene chiamato il getter per isGreen 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: informazioni sui destinatari nullabili

La classe che estendi è chiamata ricevitore ed è possibile renderla nullable. Se lo fai, la variabile this utilizzata nel corpo può essere null, quindi assicurati di eseguire il test. Ti consigliamo di utilizzare un ricevitore nullable se prevedi che i chiamanti vogliano chiamare il tuo metodo di estensione su variabili nullable o se vuoi fornire un comportamento predefinito quando la funzione viene applicata a null.

  1. Continuando a lavorare in REPL, definisci un metodo pull() che accetta un ricevitore nullable. Ciò è 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-punto-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, la funzione println() interna non viene chiamata.

Le funzioni di estensione sono molto potenti e la maggior parte della libreria standard Kotlin è implementata come funzioni di estensione.

In questa lezione hai scoperto di più sulle raccolte, sulle costanti e hai avuto un assaggio della potenza delle funzioni e delle proprietà di estensione.

  • Le coppie e le triple possono essere utilizzate 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().
  • Un HashMap può essere utilizzato per mappare le chiavi ai valori. Ad esempio:
    val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  • Dichiara le costanti in fase di compilazione utilizzando la parola chiave const. Puoi inserirli nel livello superiore, organizzarli in un oggetto singleton o inserirli in un oggetto complementare.
  • Un oggetto companion è un oggetto singleton all'interno di una definizione di classe, definito con la parola chiave companion.
  • Le funzioni e le proprietà delle estensioni possono aggiungere funzionalità a una classe. Ad esempio:
    fun String.hasSpaces() = find { it == ' ' } != null
  • Un ricevitore nullable ti consente di creare estensioni su una classe che può essere null. L'operatore ?. può essere abbinato a apply per verificare la presenza di null prima di eseguire il codice. Ad esempio:
    this?.apply { println("removing $this") }

Documentazione di Kotlin

Se vuoi maggiori informazioni su un argomento di questo corso o se hai difficoltà, https://kotlinlang.org è il punto di partenza migliore.

Tutorial di Kotlin

Il sito web https://try.kotlinlang.org include tutorial dettagliati 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 Kotlin Bootcamp for Programmers.

IntelliJ IDEA

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

Questa sezione elenca i possibili compiti a casa per gli studenti che seguono questo codelab nell'ambito di un corso guidato da un insegnante. Spetta all'insegnante:

  • Assegna i compiti, se richiesto.
  • Comunica agli studenti come inviare i compiti.
  • Valuta i compiti a casa.

Gli insegnanti possono utilizzare questi suggerimenti nella misura che ritengono opportuna e sono liberi di assegnare qualsiasi altro compito a casa che ritengono appropriato.

Se stai seguendo questo codelab in autonomia, sentiti libero di utilizzare questi compiti per casa per mettere alla prova le tue conoscenze.

Rispondi a queste domande

Domanda 1

Quale delle seguenti funzioni 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) genererà un errore del compilatore?

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?

▢ al livello principale di un file

▢ nelle classi regolari

▢ negli oggetti singleton

▢ in oggetti associati

Passa alla lezione successiva: 5.2 Generics

Per una panoramica del corso, inclusi i link ad altri codelab, vedi "Kotlin Bootcamp for Programmers: Welcome to the course" (Kotlin Bootcamp per programmatori: benvenuto al corso).