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, creerai un programma Kotlin e scoprirai lezioni e oggetti in Kotlin. Gran parte di questi contenuti ti sarà familiare se conosci un'altra lingua orientata agli oggetti, ma Kotlin presenta alcune differenze importanti per ridurre la quantità di codice da scrivere. Scoprirai anche di più sulle classi astratte e sulla delega dell'interfaccia.
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
- Nozioni di base su Kotlin, tra cui tipi, operatori e looping
- Sintassi delle funzioni di Kotlin
- Nozioni di base della programmazione orientata agli oggetti
- Nozioni di base su un IDE, come IntelliJ IDEA o Android Studio
Obiettivi didattici
- Come creare corsi e accedere alle proprietà in Kotlin
- Come creare e utilizzare costruttori di classe in Kotlin
- Come creare una sottoclasse e come funziona l'ereditarietà
- Informazioni su classi, interfacce e delega di interfaccia astratte
- Come creare e utilizzare le classi di dati
- Come utilizzare singleton, enumerazioni e classi sigillate
In questo lab proverai a:
- Creare una classe con proprietà
- Creare un costruttore per un corso
- Crea una classe secondaria
- Esamina esempi di interfacce e classi astratte
- Creare una semplice classe di dati
- Scopri di più su singleton, enumerazioni e classi chiuse
Dovresti già conoscere i seguenti termini di programmazione:
- Le classi sono progetti per gli oggetti. Ad esempio, una classe
Aquarium
è il progetto base per creare un oggetto acquario. - Gli oggetti sono istanze di classi; un oggetto acquario è una
Aquarium
effettiva. - Le proprietà sono caratteristiche delle classi, come la lunghezza, la larghezza e l'altezza di un
Aquarium
. - I metodi, chiamati anche funzionalità membri, sono la funzionalità del corso. I metodi sono ciò che puoi fare con l'oggetto, Ad esempio, puoi
fillWithWater()
un oggettoAquarium
. - Un'interfaccia è una specifica che può essere implementata da una classe. Ad esempio, la pulizia è comune a oggetti diversi dagli acquari, e in genere la pulizia avviene in modi simili a seconda dei diversi oggetti. Potresti avere un'interfaccia chiamata
Clean
che definisce un metodoclean()
. La classeAquarium
potrebbe implementare l'interfacciaClean
per pulire l'acquario con una spugna morbida. - I pacchetti sono un modo per raggruppare il codice correlato per mantenerlo organizzato o per creare una libreria di codice. Una volta creato un pacchetto, puoi importare i contenuti del pacchetto in un altro file e riutilizzare il codice e le classi al suo interno.
In questa attività creerai un nuovo pacchetto e una classe con alcune proprietà e un metodo.
Passaggio 1: crea un pacchetto
I pacchetti possono aiutarti a organizzare il codice.
- Nel riquadro Progetto, sotto il progetto Hello Kotlin, fai clic con il pulsante destro del mouse sulla cartella src.
- Seleziona Nuovo > pacchetto e chiamalo
example.myapp
.
Passaggio 2: crea un corso con le proprietà
I corsi sono definiti con la parola chiave class
e i nomi delle classi iniziano per convenzione con una lettera maiuscola.
- Fai clic con il pulsante destro del mouse sul pacchetto example.myapp.
- Seleziona New > Kotlin File / Class (Nuovo > file/classe Kotlin).
- In Tipo, seleziona Classe e denomina il corso
Aquarium
. IntelliJ IDEA include il nome del pacchetto nel file e crea automaticamente una classeAquarium
vuota. - All'interno della classe
Aquarium
, definisci e inizializza le proprietàvar
per la larghezza, l'altezza e la lunghezza (in centimetri). Inizializza le proprietà con valori predefiniti.
package example.myapp
class Aquarium {
var width: Int = 20
var height: Int = 40
var length: Int = 100
}
Funzioni avanzate: Kotlin crea automaticamente getter e setter per le proprietà che hai definito nella classe Aquarium
, consentendoti di accedere direttamente alle proprietà, ad esempio myAquarium.length
.
Passaggio 3: crea una funzione main()
Crea un nuovo file denominato main.kt
per contenere la funzione main()
.
- Nel riquadro di Progetto a sinistra, fai clic con il pulsante destro del mouse sul pacchetto example.myapp.
- Seleziona New > Kotlin File / Class (Nuovo > file/classe Kotlin).
- Nel menu a discesa Tipo, mantieni la selezione come File e denomina il file
main.kt
. IntelliJ IDEA include il nome del pacchetto, ma non include una definizione della classe per il file. - Definisci una funzione
buildAquarium()
e, all'interno, crea un'istanza diAquarium
. Per creare un'istanza, fai riferimento alla classe come se fosse una funzione,Aquarium()
. Viene chiamato il costruttore della classe e viene creata un'istanza della classeAquarium
, come se utilizzassinew
in altre lingue. - Definisci una funzione
main()
e chiamabuildAquarium()
.
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium()
}
fun main() {
buildAquarium()
}
Passaggio 4: aggiungi un metodo
- Nel corso
Aquarium
, aggiungi un metodo per stampare le proprietà delle dimensioni dell'acquario.
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
}
- In
main.kt
, abuildAquarium()
, chiama il metodoprintSize()
sumyAquarium
.
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
}
- Esegui il programma facendo clic sul triangolo verde accanto alla funzione
main()
. Osserva il risultato.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm
- In
buildAquarium()
, aggiungi il codice per impostare l'altezza su 60 e stampare le proprietà delle dimensioni modificate.
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
myAquarium.height = 60
myAquarium.printSize()
}
- Esegui il programma e osserva l'output.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 100 cm Height: 60 cm
In questa attività creerai un costruttore per il corso e continuerai a lavorare con le proprietà.
Passaggio 1: crea un costruttore
In questo passaggio devi aggiungere un costruttore alla classe Aquarium
che hai creato nella prima attività. Nell'esempio precedente, ogni istanza di Aquarium
viene creata con le stesse dimensioni. Una volta creata le proprietà, puoi modificarne le dimensioni impostandone le proprietà, ma sarebbe più semplice crearla con le dimensioni corrette.
In alcuni linguaggi di programmazione, il costruttore viene definito creando un metodo all'interno della classe che ha lo stesso nome della classe. In Kotlin definisci il costruttore direttamente nella dichiarazione della classe stessa, specificando i parametri tra parentesi come se la classe fosse un metodo. Come nel caso delle funzioni in Kotlin, questi parametri possono includere valori predefiniti.
- Nella classe
Aquarium
creata in precedenza, modifica la definizione della classe per includere tre parametri costruttore con valori predefiniti perlength
,width
eheight
e assegnali alle proprietà corrispondenti.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
// Dimensions in cm
var length: Int = length
var width: Int = width
var height: Int = height
...
}
- Il modo più compatto per Kotlin è definire le proprietà direttamente con il costruttore, utilizzando
var
oval
; inoltre, Kotlin crea automaticamente anche i getter e i setter. Successivamente, puoi rimuovere le definizioni delle proprietà dal corpo del corso.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
- Quando crei un oggetto
Aquarium
con quel costruttore, non puoi specificare argomenti e ottenere i valori predefiniti, o specificarne solo alcuni, oppure specificarli tutti e creare unAquarium
completamente personalizzato. Nella funzionebuildAquarium()
, prova diversi modi per creare un oggettoAquarium
utilizzando i parametri denominati.
fun buildAquarium() {
val aquarium1 = Aquarium()
aquarium1.printSize()
// default height and length
val aquarium2 = Aquarium(width = 25)
aquarium2.printSize()
// default width
val aquarium3 = Aquarium(height = 35, length = 110)
aquarium3.printSize()
// everything custom
val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
aquarium4.printSize()
}
- Esegui il programma e osserva l'output.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 25 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 110 cm Height: 35 cm Width: 25 cm Length: 110 cm Height: 35 cm
Tieni presente che non devi sovraccaricare il costruttore e scrivere una versione diversa per ognuno di questi casi (e alcuni altri per le altre combinazioni). Kotlin crea i dati necessari dai valori predefiniti e dai parametri denominati.
Passaggio 2: aggiungi i blocchi di inizializzazione
I costruttori di esempio riportati sopra si limitano a dichiarare proprietà e assegnare loro il valore di un'espressione. Se il costruttore ha bisogno di più codice di inizializzazione, può essere inserito in uno o più blocchi init
. In questo passaggio devi aggiungere alcuni blocchi init
alla classe Aquarium
.
- Nel corso
Aquarium
, aggiungi un bloccoinit
per stampare che l'oggetto è in fase di inizializzazione e un secondo blocco per stampare il volume in litri.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
init {
println("aquarium initializing")
}
init {
// 1 liter = 1000 cm^3
println("Volume: ${width * length * height / 1000} l")
}
}
- Esegui il programma e osserva l'output.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm
Nota che i blocchi init
vengono eseguiti nell'ordine in cui sono visualizzati nella definizione della classe e vengono tutti eseguiti quando viene chiamato il costruttore.
Passaggio 3: scopri i costruttori secondari
In questo passaggio, imparerai i costruttori secondari e ne aggiungerai uno alla tua classe. Oltre a un costruttore principale, che può avere uno o più blocchi init
, una classe Kotlin può avere anche uno o più costruttori secondari che consentono il sovraccarico dei costruttori, ovvero costruttori con argomenti diversi.
- Nella classe
Aquarium
, aggiungi un costruttore secondario che consideri diversi pesci come argomento, utilizzando la parola chiaveconstructor
. Crea una proprietà della vascaval
per il volume calcolato dell'acquario in litri, basato sul numero di pesci. Assumi 2 litri (2.000 cm^3) di acqua per pesce, più un po' di spazio in più per evitare l'acqua.
constructor(numberOfFish: Int) : this() {
// 2,000 cm^3 per fish + extra room so water doesn't spill
val tank = numberOfFish * 2000 * 1.1
}
- All'interno del costruttore secondario, mantieni la stessa lunghezza e larghezza (impostate nel costruttore principale) e calcola l'altezza necessaria per rendere il serbatoio il volume sopra indicato.
// calculate the height needed
height = (tank / (length * width)).toInt()
- Nella funzione
buildAquarium()
, aggiungi una chiamata per creare unAquarium
utilizzando il nuovo costruttore secondario. Stampa le dimensioni e il volume.
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
- Esegui il programma e osserva l'output.
⇒ aquarium initializing Volume: 80 l Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
Nota che il volume viene stampato due volte, una dal blocco init
nel costruttore principale prima che venga eseguito il costruttore secondario e una volta dal codice in buildAquarium()
.
Potresti aver incluso anche la parola chiave constructor
nel costruttore principale, ma nella maggior parte dei casi non è necessaria.
Passaggio 4: aggiungi un nuovo getter proprietà
In questo passaggio devi aggiungere un getter di proprietà esplicito. Kotlin definisce automaticamente getter e setter quando definisci le proprietà, ma a volte il valore di una proprietà deve essere adeguato o calcolato. Ad esempio, in alto hai stampato il volume di Aquarium
. Puoi rendere il volume disponibile come proprietà definendo una variabile e un getter. Poiché volume
deve essere calcolato, il getter deve restituire il valore calcolato, che puoi fare con una funzione di una riga.
- Nella classe
Aquarium
, definisci una proprietàInt
denominatavolume
e definisci un metodoget()
che calcoli il volume nella riga successiva.
val volume: Int
get() = width * height * length / 1000 // 1000 cm^3 = 1 l
- Rimuovi il blocco
init
che stampa il volume. - Rimuovi il codice in
buildAquarium()
che stampa il volume. - Nel metodo
printSize()
, aggiungi una riga per stampare il volume.
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm "
)
// 1 l = 1000 cm^3
println("Volume: $volume l")
}
- Esegui il programma e osserva l'output.
⇒ aquarium initializing Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
Le dimensioni e il volume sono le stesse di prima, ma il volume viene stampato una sola volta dopo che l'oggetto è stato completamente inizializzato dal costruttore principale e dal costruttore secondario.
Passaggio 5: aggiungi un setter proprietà
In questo passaggio, creerai un nuovo setter proprietà per il volume.
- Nel corso
Aquarium
, impostavolume
suvar
, in modo che possa essere impostato più di una volta. - Aggiungi un setter per la proprietà
volume
aggiungendo un metodoset()
sotto il getter, che ricalcola l'altezza in base alla quantità d'acqua fornita. Per convenzione, il nome del parametro setter èvalue
, ma se preferisci puoi modificarlo.
var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- In
buildAquarium()
, aggiungi un codice per impostare il volume dell'acquario a 70 litri. Stampa la nuova dimensione.
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
aquarium6.volume = 70
aquarium6.printSize()
}
- Esegui di nuovo il programma e osserva l'altezza e il volume modificati.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm
Volume: 70 l
Finora non sono stati modificati modificatori, quali public
o private
. Questo perché, per impostazione predefinita, tutto in Kotlin è pubblico, il che significa che è possibile accedere a tutti ovunque, compresi corsi, metodi, proprietà e variabili relative ai membri.
In Kotlin, le classi, gli oggetti, le interfacce, i costruttori, le funzioni, le proprietà e i loro setter possono avere modificatori di visibilità:
public
indica che non è visibile nella classe. Per impostazione predefinita, tutto è pubblico, incluse variabili e metodi del corso.internal
significa che sarà visibile solo all'interno di quel modulo. Un modulo è un insieme di file Kotlin raccolti insieme, ad esempio una biblioteca o un'applicazione.private
significa che sarà visibile solo in quella classe (o nel file di origine, se lavori con le funzioni).protected
è uguale aprivate
, ma sarà visibile anche nelle eventuali classi secondarie.
Per ulteriori informazioni, consulta la sezione Modificatori di visibilità nella documentazione di Kotlin.
Variabili membri
Le proprietà all'interno di una classe o di variabili membri sono public
per impostazione predefinita. Se li definisci con var
, sono modificabili, ovvero leggibili e scrivibili. Se li definisci con val
, saranno di sola lettura dopo l'inizializzazione.
Se vuoi una proprietà che il tuo codice possa leggere o scrivere, ma all'esterno il codice può solo leggere, puoi lasciare privati la proprietà e il getter e dichiarare il setter privato, come mostrato di seguito.
var volume: Int
get() = width * height * length / 1000
private set(value) {
height = (value * 1000) / (width * length)
}
In questa attività imparerai come funzionano le sottoclassi e l'ereditarietà in Kotlin. Sono simili a quanto hai visto in altre lingue, ma esistono alcune differenze.
In Kotlin, per impostazione predefinita, i corsi non possono essere suddivisi in sottoclassi. Analogamente, le proprietà e le variabili dei membri non possono essere sostituite da sottoclassi (anche se è possibile accedervi).
Devi contrassegnare un corso come open
per consentire che venga classificato come secondario. Analogamente, devi contrassegnare le proprietà e le variabili membro come open
per poterle sostituire nella sottoclasse. La parola chiave open
è obbligatoria per evitare fughe di informazioni dettagliate sull'implementazione nell'interfaccia della classe.
Passaggio 1: apri la lezione sull'acquario
In questo passaggio, devi creare la classe Aquarium
open
, in modo da poterla sostituire nel passaggio successivo.
- Contrassegna la classe
Aquarium
e tutte le sue proprietà con la parola chiaveopen
.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
open var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- Aggiungi una proprietà
shape
aperta con il valore"rectangle"
.
open val shape = "rectangle"
- Aggiungi una proprietà
water
aperta con un getter che restituisce il 90% del volume diAquarium
.
open var water: Double = 0.0
get() = volume * 0.9
- Aggiungi il codice al metodo
printSize()
per stampare la forma e la quantità di acqua come percentuale del volume.
fun printSize() {
println(shape)
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
// 1 l = 1000 cm^3
println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
- In
buildAquarium()
, modifica il codice per creare unAquarium
conwidth = 25
,length = 25
eheight = 40
.
fun buildAquarium() {
val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
aquarium6.printSize()
}
- Esegui il programma e osserva il nuovo output.
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full)
Passaggio 2: crea una sottoclasse
- Crea una sottoclasse di
Aquarium
chiamataTowerTank
, che implementa un serbatoio a cilindro arrotondato anziché un serbatoio rettangolare. Puoi aggiungereTowerTank
sottoAquarium
, poiché puoi aggiungere un altro corso nello stesso file della classeAquarium
. - In
TowerTank
, sostituisci la proprietàheight
, definita nel costruttore. Per sostituire una proprietà, utilizza la parola chiaveoverride
nella sottoclasse.
- Crea il costruttore per
TowerTank
utilizzando undiameter
. Usadiameter
perlength
ewidth
quando chiami il costruttore nella superclasseAquarium
.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
- Esegui l'override della proprietà del volume per calcolare un cilindro. La formula per un cilindro è pi greco per il raggio al quadrato per l'altezza. Devi importare la costante
PI
dajava.lang.Math
.
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
- In
TowerTank
, sostituisci la proprietàwater
all'80% del volume.
override var water = volume * 0.8
- Sostituisci
shape
in modo che sia"cylinder"
.
override val shape = "cylinder"
- Il corso finale di
TowerTank
dovrebbe essere simile al seguente codice.
Aquarium.kt
:
package example.myapp
import java.lang.Math.PI
... // existing Aquarium class
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
override var water = volume * 0.8
override val shape = "cylinder"
}
- In
buildAquarium()
, crea unTowerTank
con un diametro di 25 cm e un'altezza di 45 cm. Stampa le dimensioni.
main.kt:
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium(width = 25, length = 25, height = 40)
myAquarium.printSize()
val myTower = TowerTank(diameter = 25, height = 40)
myTower.printSize()
}
- Esegui il programma e osserva l'output.
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full) aquarium initializing cylinder Width: 25 cm Length: 25 cm Height: 40 cm Volume: 18 l Water: 14.4 l (80.0% full)
A volte vuoi definire proprietà o comportamento comuni da condividere tra le classi correlate. Kotlin offre due modi per farlo: le interfacce e le classi astratte. In questa attività creerai una classe AquariumFish
astratta per le proprietà comuni a tutti i pesci. Crei un'interfaccia chiamata FishAction
per definire il comportamento comune a tutti i pesci.
- Non è possibile creare autonomamente un'istanza di una classe astratta o di un'interfaccia, il che significa che non puoi creare oggetti di quel tipo direttamente.
- Le classi astratte hanno costruttori.
- Le interfacce non possono avere logiche di costruzione o archiviare stato.
Passaggio 1. Creare una classe astratta
- In example.myapp, crea un nuovo file,
AquariumFish.kt
. - Crea un corso, chiamato anche
AquariumFish
, e contrassegnalo conabstract
. - Aggiungi una proprietà
String
,color
e contrassegnala conabstract
.
package example.myapp
abstract class AquariumFish {
abstract val color: String
}
- Crea due sottoclassi di
AquariumFish
,Shark
ePlecostomus
. color
è astratto, per cui le sottoclassi devono implementarlo. Fai diventareShark
grigio ePlecostomus
oro.
class Shark: AquariumFish() {
override val color = "gray"
}
class Plecostomus: AquariumFish() {
override val color = "gold"
}
- In main.kt, crea una funzione
makeFish()
per testare i tuoi corsi. Crea un'istanza diShark
ePlecostomus
, quindi stampa il colore di ciascuno. - Elimina il tuo codice di test precedente in
main()
e aggiungi una chiamata amakeFish()
. Il tuo codice sarà simile al seguente:
main.kt
:
package example.myapp
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
println("Plecostomus: ${pleco.color}")
}
fun main () {
makeFish()
}
- Esegui il programma e osserva l'output.
⇒ Shark: gray Plecostomus: gold
Il seguente diagramma rappresenta la classe Shark
e la classe Plecostomus
, che sottoclasse la classe astratta, AquariumFish
.
Passaggio 2. Creare un'interfaccia
- In AquariumFish.kt, crea un'interfaccia chiamata
FishAction
con un metodoeat()
.
interface FishAction {
fun eat()
}
- Aggiungi
FishAction
a ciascuna delle sottoclassi e implementaeat()
chiedendogli di stampare le operazioni eseguite dai pesci.
class Shark: AquariumFish(), FishAction {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
class Plecostomus: AquariumFish(), FishAction {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- Nella funzione
makeFish()
, chiedi a ogni pesce che hai creato di mangiare qualcosa chiamandoeat()
.
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
shark.eat()
println("Plecostomus: ${pleco.color}")
pleco.eat()
}
- Esegui il programma e osserva l'output.
⇒ Shark: gray hunt and eat fish Plecostomus: gold eat algae
Il seguente diagramma rappresenta la classe Shark
e la classe Plecostomus
, entrambe composte e implementate nell'interfaccia di FishAction
.
Quando utilizzare le classi astratte e le interfacce
Gli esempi riportati sopra sono semplici, ma se hai molte classi interconnesse, classi astratte e interfacce possono aiutarti a rendere il tuo design più pulito, organizzato e più facile da gestire.
Come indicato sopra, le classi astratte possono avere costruttori e le interfacce non possono, ma sono molto simili. Quando è consigliabile utilizzarle?
Quando utilizzi interfacce per scrivere una classe, la funzionalità di quest'ultima viene estesa tramite le istanze della classe che contiene. La composizione tende a semplificare il riutilizzo e sul motivo dell'ereditarietà da parte di una classe astratta. Inoltre, puoi utilizzare più interfacce in un corso, ma puoi eseguire la sottoclasse solo da una classe astratta.
La composizione spesso porta a un incapsulamento migliore, a un accoppiamento inferiore (interdipendenza), a interfacce più pulite e a un codice più utilizzabile. Per questi motivi, l'utilizzo della composizione con le interfacce è il design preferito. D'altra parte, l'ereditarietà da una classe astratta tende a essere la scelta più naturale per alcuni problemi. Perciò, è meglio preferire la composizione, ma se l'ereditarietà ha senso, Kotlin ti consente di farlo anche tu.
- Utilizza un'interfaccia se hai molti metodi e una o due implementazioni predefinite, ad esempio in
AquariumAction
di seguito.
interface AquariumAction {
fun eat()
fun jump()
fun clean()
fun catchFish()
fun swim() {
println("swim")
}
}
- Puoi utilizzare una classe astratta ogni volta che non puoi completarla. Ad esempio, se torni alla classe
AquariumFish
, puoi rendere tutti gli strumentiAquariumFish
implementatiFishAction
e fornire un'implementazione predefinita pereat
, lasciando sfocatocolor
, perché non c'è davvero un colore predefinito per i pesci.
interface FishAction {
fun eat()
}
abstract class AquariumFish: FishAction {
abstract val color: String
override fun eat() = println("yum")
}
L'attività precedente introduceva classi astratte, interfacce e l'idea della composizione. La delega dell'interfaccia è una tecnica avanzata in cui i metodi di un'interfaccia sono implementati da un oggetto helper (o delegato), che viene poi utilizzato da una classe. Questa tecnica può essere utile quando utilizzi un'interfaccia in una serie di classi non correlate: aggiungi la funzionalità di interfaccia necessaria a una classe helper separata e ognuna delle classi utilizza un'istanza della classe helper per implementare la funzionalità.
In questa attività, utilizzerai la delega dell'interfaccia per aggiungere funzionalità a un corso.
Passaggio 1: crea una nuova interfaccia
- In AquariumFish.kt, rimuovi la classe
AquariumFish
. Invece di ereditare dalla classeAquariumFish
,Plecostomus
eShark
implementeranno interfacce sia per l'azione pesci che per il colore. - Crea una nuova interfaccia,
FishColor
, che definisce il colore come stringa.
interface FishColor {
val color: String
}
- Modifica
Plecostomus
per implementare due interfacce,FishAction
eFishColor
. Devi sostituirecolor
daFishColor
eeat()
daFishAction
.
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- Cambia la classe
Shark
per implementare anche le due interfacce,FishAction
eFishColor
, anziché ereditare daAquariumFish
.
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
- Il codice completato sarà simile al seguente:
package example.myapp
interface FishAction {
fun eat()
}
interface FishColor {
val color: String
}
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
Passaggio 2: crea una lezione su singleton
Ora implementerai la configurazione della parte della delega creando una classe helper che implementa FishColor
. Crei una classe base chiamata GoldColor
che implementa FishColor
, il suo colore è oro.
Non ha senso creare più istanze di GoldColor
, perché fanno tutte la stessa cosa. Pertanto, Kotlin ti consente di dichiarare una classe in cui è possibile crearne una sola istanza utilizzando la parola chiave object
invece di class
. Kotlin creerà questa istanza e vi farà riferimento il nome della classe. Quindi tutti gli altri oggetti possono utilizzare solo questa istanza: non è possibile creare altre istanze di questa classe. Se conosci il pattern singleton, ecco come implementare i singleton in Kotlin.
- In AquariumFish.kt, crea un oggetto per
GoldColor
. Sostituire il colore.
object GoldColor : FishColor {
override val color = "gold"
}
Passaggio 3: aggiungi la delega dell'interfaccia per FishColor
Ora puoi utilizzare la delega dell'interfaccia.
- In AquariumFish.kt, rimuovi l'override di
color
daPlecostomus
. - Modifica la classe
Plecostomus
per prendere il colore daGoldColor
. Per farlo, aggiungiby GoldColor
alla dichiarazione del corso e crea la delega. Ciò significa che anziché utilizzareFishColor
, utilizza l'implementazione fornita daGoldColor
. Pertanto, ogni volta che si accede acolor
, viene delegata aGoldColor
.
class Plecostomus: FishAction, FishColor by GoldColor {
override fun eat() {
println("eat algae")
}
}
Con la classe così com'è, tutti i Plecos saranno dorati, ma questi pesci sono in realtà disponibili in molti colori. Per risolvere il problema, aggiungi un parametro costruttore per il colore con GoldColor
come colore predefinito per Plecostomus
.
- Modifica la classe
Plecostomus
in modo che passi un pass infishColor
con il costruttore e ne imposti il valore predefinito suGoldColor
. Cambia la delega daby GoldColor
aby fishColor
.
class Plecostomus(fishColor: FishColor = GoldColor): FishAction,
FishColor by fishColor {
override fun eat() {
println("eat algae")
}
}
Passaggio 4: aggiungi la delega dell'interfaccia per FishAction
Analogamente, puoi utilizzare la delega dell'interfaccia per FishAction
.
- In AquariumFish.kt crea una classe
PrintingFishAction
che implementiFishAction
, che richiede unString
,food
, quindi stampa quello che mangia il pesce.
class PrintingFishAction(val food: String) : FishAction {
override fun eat() {
println(food)
}
}
- Nella classe
Plecostomus
, rimuovi la funzione di overrideeat()
perché verrà sostituita con una delega. - Nella dichiarazione di
Plecostomus
, delegaFishAction
aPrintingFishAction
, superando"eat algae"
. - Con tutta la delega, non esiste alcun codice nel corpo della classe
Plecostomus
, quindi rimuovi{}
, perché tutte le sostituzioni sono gestite dalla delega dell'interfaccia
class Plecostomus (fishColor: FishColor = GoldColor):
FishAction by PrintingFishAction("eat algae"),
FishColor by fishColor
Il seguente diagramma rappresenta le classi Shark
e Plecostomus
, entrambe composte dalle interfacce PrintingFishAction
e FishColor
, ma relative alla delega dell'implementazione.
La delega nell'interfaccia è potente e in genere dovresti considerare di usarla quando potresti utilizzare una classe astratta in un'altra lingua. Ti consente di utilizzare la composizione per collegare i comportamenti, invece di richiedere molte classi secondarie, ognuna delle quali è specializzata in un modo diverso.
Una classe di dati è simile a un struct
in alcune altre lingue: esiste principalmente per contenere alcuni dati, ma un oggetto di classe di dati è ancora un oggetto. Gli oggetti Kotlin per la classe dati presentano alcuni vantaggi aggiuntivi, come le utilità per la stampa e la copia. In questa attività creerai una semplice classe dati e imparerai come il supporto fornito da Kotlin per le classi di dati.
Passaggio 1: crea una classe di dati
- Aggiungi un nuovo pacchetto
decor
nel pacchetto example.myapp per contenere il nuovo codice. Fai clic con il pulsante destro del mouse su example.myapp nel riquadro Progetto e seleziona File > Nuovo > pacchetto. - Nel pacchetto, crea un nuovo corso chiamato
Decoration
.
package example.myapp.decor
class Decoration {
}
- Per rendere
Decoration
una classe di dati, aggiungi il prefissodata
alla dichiarazione della classe. - Aggiungi una proprietà
String
denominatarocks
per fornire alla classe alcuni dati.
data class Decoration(val rocks: String) {
}
- Nel file, all'esterno della classe, aggiungi una funzione
makeDecorations()
per creare e stampare un'istanza diDecoration
con"granite"
.
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
}
- Aggiungi una funzione
main()
per chiamaremakeDecorations()
ed esegui il programma. Nota l'output ragionevole che viene creato perché si tratta di una classe di dati.
⇒ Decoration(rocks=granite)
- In
makeDecorations()
, crea un'istanza di altri due oggettiDecoration
sia "quot;slate" e "stampa".
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
val decoration2 = Decoration("slate")
println(decoration2)
val decoration3 = Decoration("slate")
println(decoration3)
}
- In
makeDecorations()
, aggiungi un'istruzione di stampa che stampi il risultato del confronto tradecoration1
edecoration2
e un secondo che confrontidecoration3
condecoration2
. Utilizza il metodo equals() fornito dalle classi di dati.
println (decoration1.equals(decoration2))
println (decoration3.equals(decoration2))
- Esegui il codice.
⇒ Decoration(rocks=granite) Decoration(rocks=slate) Decoration(rocks=slate) false true
Passaggio 2. Utilizza struttura
Per ottenere le proprietà di un oggetto di dati e assegnarle alle variabili, puoi assegnarle una alla volta, in questo modo.
val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver
Puoi invece creare variabili, una per ogni proprietà, e assegnare l'oggetto dati al gruppo di variabili. Kotlin inserisce il valore della proprietà in ogni variabile.
val (rock, wood, diver) = decoration
Questa operazione è chiamata organizzazione ed è un'abbreviazione utile. Il numero di variabili deve corrispondere al numero di proprietà e le variabili vengono assegnate nell'ordine in cui vengono dichiarate nella classe. Ecco un esempio completo che puoi provare con Decoration.kt.
// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}
fun makeDecorations() {
val d5 = Decoration2("crystal", "wood", "diver")
println(d5)
// Assign all properties to variables.
val (rock, wood, diver) = d5
println(rock)
println(wood)
println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver) crystal wood diver
Se non hai bisogno di una o più proprietà, puoi ignorarle utilizzando _
anziché il nome di una variabile, come mostrato nel codice di seguito.
val (rock, _, diver) = d5
In questa attività imparerai alcune lezioni specifiche per Kotlin, tra cui:
- Lezioni di singleton
- Enum
- Lezioni chiuse
Passaggio 1: richiama le lezioni singole
Ricordi l'esempio precedente con la classe GoldColor
.
object GoldColor : FishColor {
override val color = "gold"
}
Poiché ogni istanza di GoldColor
funziona in modo analogo, viene dichiarata come object
anziché come class
per renderla singola. Può esserci solo un'istanza.
Passaggio 2: crea un'enumerazione
Kotlin supporta anche le enumerazioni, che consentono di enumerare gli elementi e farvi riferimento per nome, in modo simile alle altre lingue. Dichiara un'enumerazione preceduta dalla parola chiave enum
. Per una dichiarazione di enumerazione di base è necessario solo un elenco di nomi, ma puoi anche definire uno o più campi associati a ogni nome.
- In Decoration.kt, prova un esempio di enumerazione.
enum class Color(val rgb: Int) {
RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}
Le enumerazioni sono come dei singleton, in quanto l'enumerazione di uno solo può essere solo uno dei due. Ad esempio, possono esistere un solo elemento Color.RED
, Color.GREEN
e Color.BLUE
. In questo esempio, i valori RGB sono assegnati alla proprietà rgb
per rappresentare i componenti di colore. Puoi anche ottenere il valore ordinale di un'enumerazione utilizzando la proprietà ordinal
e il nome tramite la proprietà name
.
- Prova un altro esempio di enumerazione.
enum class Direction(val degrees: Int) {
NORTH(0), SOUTH(180), EAST(90), WEST(270)
}
fun main() {
println(Direction.EAST.name)
println(Direction.EAST.ordinal)
println(Direction.EAST.degrees)
}
⇒ EAST 2 90
Passaggio 3: crea un corso chiuso
Una classe chiusa è una classe sottoclasse, ma solo all'interno del file in cui è stata dichiarata. Se provi a suddividere la classe in un file diverso, verrà visualizzato un errore.
Poiché classi e sottoclassi sono nello stesso file, Kotlin conoscerà tutte le sottoclassi in modo statico. In altre parole, al momento della compilazione, il compilatore vede tutte le classi e le sottoclassi e sa che sono tutte, in modo da poter eseguire ulteriori controlli.
- In AquariumFish.kt, prova un esempio di classe sigillata, tenendo presente il tema acquatico.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()
fun matchSeal(seal: Seal): String {
return when(seal) {
is Walrus -> "walrus"
is SeaLion -> "sea lion"
}
}
La classe Seal
non può essere classificata in un altro file. Se vuoi aggiungere altri tipi di Seal
, devi aggiungerli nello stesso file. In questo modo le classi sigillate sono un modo sicuro per rappresentare un numero fisso di tipi. Ad esempio, le classi sigillate sono un'ottima soluzione per restituire un errore o un errore a un'API di rete.
Questa lezione ha trattato molti argomenti. Gran parte di questo processo dovrebbe essere familiare da altri linguaggi di programmazione orientati agli oggetti, ma Kotlin aggiunge alcune funzionalità per mantenere il codice conciso e leggibile.
Classi e costruttori
- Definisci una classe in Kotlin utilizzando
class
. - Kotlin crea automaticamente setter e getter per le proprietà.
- Definisci il costruttore principale direttamente nella definizione della classe. Ad esempio:
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
- Se un costruttore principale necessita di codice aggiuntivo, scrivilo in uno o più blocchi
init
- Una classe può definire uno o più costruttori secondari utilizzando
constructor
, ma lo stile Kotlin consiste nell'utilizzare una funzione di fabbrica.
Modificatori e sottoclassi di visibilità
- Tutte le classi e funzioni in Kotlin sono
public
per impostazione predefinita, ma puoi usare i modificatori per cambiare la visibilità ininternal
,private
oprotected
. - Per creare una sottoclasse, la classe principale deve essere contrassegnata come
open
. - Per eseguire l'override di metodi e proprietà in una sottoclasse, i metodi e le proprietà devono essere contrassegnati come
open
nella classe principale. - Una classe sigillata può essere classificata solo nello stesso file in cui è definita. Crea una classe sigillata preceduta dalla dichiarazione
sealed
.
Classi di dati, singleton ed enumerazioni
- Crea una classe di dati preceduta dalla dichiarazione
data
. - Struttura è un modo rapido per assegnare le proprietà di un oggetto
data
a variabili separate. - Crea una classe singleton utilizzando
object
anzichéclass
. - Definisci un'enumerazione utilizzando
enum class
.
Classi, interfacce e delega astratte
- Le classi e le interfacce astratte sono due modi per condividere il comportamento comune tra le classi.
- Una classe astratta definisce proprietà e comportamento, ma lascia l'implementazione alle sottoclassi.
- Un'interfaccia definisce il comportamento e può fornire implementazioni predefinite per alcuni o tutti i comportamenti.
- Quando utilizzi interfacce per scrivere una classe, la funzionalità di quest'ultima viene estesa tramite le istanze della classe che contiene.
- La delega di interfaccia utilizza la composizione, ma delega anche l'implementazione alle classi di interfaccia.
- La composizione è un modo efficace per aggiungere funzionalità a una classe utilizzando la delega dell'interfaccia. In generale, la composizione è preferita, ma l'ereditarietà da una classe astratta è più adatta per alcuni problemi.
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.
- Classi ed ereditarietà
- Costruttori
- Funzioni di fabbrica
- Proprietà e campi
- Modificatori di visibilità
- Lezioni astratte
- Interfacce
- Delega
- Classi dati
- Parità
- Struttura
- Dichiarazioni degli oggetti
- Enumerazioni delle classi
- Corsi chiusi
- Gestire gli errori facoltativi utilizzando le classi sigillate Kotlin
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
I corsi hanno un metodo speciale che funge da progetto base per la creazione di oggetti di quella classe. Come si chiama il metodo?
▢ Un builder
▢ Un'istanza
▢ Un costruttore
▢ Un progetto base
Domanda 2
Quale delle seguenti affermazioni sulle interfacce e sulle classi astratte NON è corretta?
▢ Le classi astratte possono avere costruttori.
▢ Le interfacce non possono avere costruttori.
▢ Puoi creare un'istanza di interfacce e classi astratte direttamente.
▢ Le proprietà astratte devono essere implementate da sottoclassi della classe astratta.
Domanda 3
Quale dei seguenti NON è un modificatore di visibilità di Kotlin per proprietà, metodi e così via?
▢ internal
▢ nosubclass
▢ protected
▢ private
Domanda 4
Considera questa classe di dati:data class Fish(val name: String, val species:String, val colors:String)
Quale dei seguenti NON è un codice valido per creare e destrutturare un oggetto Fish
?
▢ val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")
▢ val (name2, _, colors2) = Fish("Bitey", "shark", "gray")
▢ val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")
▢ val (name4, species4, colors4) = Fish("Harry", "halibut")
Domanda 5
Supponiamo che tu possieda uno zoo con molti animali che devono essere curati tutti. Quale dei seguenti NON sarà parte dell'implementazione delle comunicazioni sanitarie?
▢ Un interface
per diversi tipi di cibo.
▢ Un corso in abstract Caretaker
da cui puoi creare diversi tipi di tutori.
▢ Un interface
per dare acqua pulita a un animale.
▢ Una classe data
per una voce di un programma di alimentazione.
Passa alla lezione successiva:
Per una panoramica del corso, inclusi i link ad altri codelab, vedi "Kotlin Bootcamp for Programs: Welcome to the Course."