Italian (Italiano) translation by Loris Pizii (you can also view the original English article)
Kotlin è un moderno linguaggio di programmazione che compone il bytecode di Java. È libero e open source e promette di rendere codifica per Android ancora più divertente.
Nell'articolo precedente, hai imparato di più sulle proprietà di Kotlin, come le proprietà di inizio inizializzazione, estensione e inline. Non solo, hai anche imparato a conoscere classi avanzate come dati, enum, nidificati e lezioni chiuse a Kotlin.
In questo post, continuerai a conoscere la programmazione orientata agli oggetti a Kotlin imparando le classi astratte, le interfacce e l'ereditarietà. Per un bonus potrai anche conoscere gli alias dei tipi.
1. Classi astratte
Kotlin supporta classi astratte - proprio come Java, queste sono classi che non si intende mai creare oggetti. Una classe astratta è incompleta o inutile senza alcune sottoclassi concrete (non astratte), da cui è possibile creare istanze di oggetti. Una sottoclasse concreta di una classe astratta implementa tutti i metodi e le proprietà definiti nella classe astratta - altrimenti la sottoclasse è anche una classe astratta!
Creiamo una classe astratta con il modificatore astratto
(simile a quello di Java).
abstract class Employee (val firstName: String, val lastName: String) { abstract fun earnings(): Double }
Si noti che non tutti i membri devono essere astratti. In altre parole, possiamo avere l'implementazione di default di metodo in una classe astratta.
abstract class Employee (val firstName: String, val lastName: String) { // ... fun fullName(): String { return lastName + " " + firstName; } }
Qui abbiamo creato la funzione non-astratta fullName ()
in un dipendente
di classe astratta. Le classi concreti (sottoclassi della classe astratta) possono ignorare l'implementazione predefinita di un metodo astratto, ma solo se il metodo ha specificato il modificatore aperto
(si informerà di più in poco tempo).
Possiamo anche memorizzare lo stato in classi astratte.
abstract class Employee (val firstName: String, val lastName: String) { // ... val propFoo: String = "bla bla" }
Anche se la classe astratta non definisce alcun metodo, dobbiamo creare una sottoclasse prima di poterla istanziarla, come nell'esempio riportato di seguito.
class Programmer(firstName: String, lastName: String) : Employee(firstName, lastName) { override fun earnings(): Double { // calculate earnings } }
La nostra classe Programmatore
estende la classe astratta Employee
. In Kotlin usiamo un carattere singolo del colon (:)
anziché il Java estende la parola chiave per estendere
una classe o implementare un'interfaccia.
Possiamo quindi creare un oggetto di tipo Programmer
e metodi di chiamata su di esso - sia nella sua classe o nella superclasse (classe di base).
val programmer = Programmer("Chike", "Mgbemena") println(programmer.fullName()) // "Mgbemena Chike"
Una cosa che potrebbe sorprendere è che abbiamo la capacità di ignorare una proprietà di val
(immutabile) con var
(mutable).
open class BaseA (open val baseProp: String) { } class DerivedA : BaseA("") { private var derivedProp: String = "" override var baseProp: String get() = derivedProp set(value) { derivedProp = value } }
Assicurati di usare questa funzionalità con saggezza! Tieni presente che non possiamo fare il reverse-override una proprietà var
con val.
2. Interfacce
Un'interfaccia è semplicemente una collezione di metodi correlati che in genere consentono di indicare agli oggetti che cosa fare e come farlo per impostazione predefinita. (Metodi predefiniti nelle interfacce sono una nuova funzionalità aggiunta a Java 8.) In altre parole, un'interfaccia è un contratto che le classi di implementazione devono rispettare.
Viene definita un'interfaccia
utilizzando la parola chiave dell'interfaccia in Kotlin (simile a Java).
class Result class Student interface StudentRepository { fun getById(id: Long): Student fun getResultsById(id: Long): List<Result> }
Nel codice precedente abbiamo dichiarato un'interfaccia StudentRepository.
Questa interfaccia contiene due metodi astratti: getById ()
e getResultsById ().
Si noti che includere la parola chiave astratta
è ridondante in un metodo di interfaccia perché sono già implicitamente astratti.
Un'interfaccia è inutile senza uno o più implementatori, quindi creiamo una classe che implementi questa interfaccia.
class StudentLocalDataSource : StudentRepository { override fun getResults(id: Long): List<Result> { // do implementation } override fun getById(id: Long): Student { // do implementation } }
Qui abbiamo creato una classe StudentLocalDataSource
che implementa l'interfaccia StudentRepository.
Utilizziamo il modificatore di override
per etichettare i metodi e le proprietà che desideriamo ridefinire dall'interfaccia o dalla superclasse: questo è simile all'annotazione @Override
in Java.
Si noti le seguenti regole aggiuntive di interfacce in Kotlin:
- Una classe può implementare tante interfacce come si desidera, ma può solo estendere una singola classe (simile a Java).
- Il modificatore di
override
è obbligatorio in Kotlin, a differenza di Java. - Oltre ai metodi, possiamo anche dichiarare le proprietà in un'interfaccia di Kotlin.
- Un metodo di interfaccia Kotlin può avere un'implementazione predefinita (simile a Java 8).
Vediamo un esempio di un metodo di interfaccia con un'implementazione predefinita.
interface StudentRepository { // ... fun delete(student: Student) { // do implementation } }
Nel codice precedente, abbiamo aggiunto un nuovo metodo delete ()
con un'implementazione predefinita (anche se non ho aggiunto il codice di implementazione effettivo a scopo dimostrativo).
Abbiamo anche la libertà di superare l'implementazione predefinita se vogliamo.
class StudentLocalDataSource : StudentRepository { // ... override fun delete(student: Student) { // do implementation } }
Come affermato, un'interfaccia di Kotlin può avere proprietà, ma nota che non è in grado di mantenere lo stato. (Tuttavia, ricorda che classi astratte possono mantenere lo stato.) Quindi la seguente definizione di interfaccia con una dichiarazione di proprietà funzionerà.
interface StudentRepository { val propFoo: Boolean // will work // ... }
Ma se cerchiamo di aggiungere un certo stato all'interfaccia assegnando un valore alla proprietà, non funzionerà.
interface StudentRepository { val propFoo: Boolean = true // Error: Property initializers are not allowed in interfaces // .. }
Tuttavia, una proprietà di interfaccia in Kotlin può avere metodi getter e setter (anche se solo quest'ultima se la proprietà è mutevole). Notare inoltre che la proprietà di un'interfaccia non può avere un campo di backup.
interface StudentRepository { var propFoo: Boolean get() = true set(value) { if (value) { // do something } } // ... }
Possiamo anche ignorare una proprietà di interfaccia se si desidera, per ridefinirla.
class StudentLocalDataSource : StudentRepository { // ... override var propFoo: Boolean get() = false set(value) { if (value) { } } }
Vediamo un caso in cui abbiamo una classe che implementa più interfacce con la stessa firma del metodo. Come decida la classe che metodo di interfaccia da chiamare?
interface InterfaceA { fun funD() {} } interface InterfaceB { fun funD() {} }
Qui abbiamo due interfacce che dispongono di un metodo con la stessa funD ().
Creiamo una classe che implementi queste due interfacce e sovrascrive il metodo funD ().
class classA : InterfaceA, InterfaceB { override fun funD() { super.funD() // Error: Many supertypes available, please specify the one you mean in angle brackets, e.g. 'super<Foo>' } }
Il compilatore è confuso circa chiamare il metodo super.funD ()
perché le due interfacce che la classe implementa hanno la stessa firma del metodo.
Per risolvere questo problema, avvolgiamo il nome dell'interfaccia per cui desideriamo chiamare il metodo in parentesi angolari . (IntelliJ IDEA
o Android Studio ti darà un suggerimento per risolvere questo problema quando si verifica).
class classA : InterfaceA, InterfaceB { override fun funD() { super<InterfaceA>.funD() } }
Qui intendiamo chiamare il metodo funD ()
di InterfaceA.
Problema risolto!
3. Eredità
Viene creata una nuova classe (sottoclasse) acquisendo un membro di una classe esistente (superclass) e forse ridefinendo l'implementazione predefinita. Questo meccanismo è conosciuto come ereditarietà nella programmazione orientata agli oggetti (OOP). Una delle cose che rendono Kotlin così impressionante è che comprende entrambi i paradigmi di programmazione funzionale e OOP - tutti in una sola lingua.
La classe di base per tutte le classi di Kotlin è Any.
class Person : Any { }
Il tipo Any
è equivalente al tipo di oggetto
che abbiamo in Java.
public open class Any { public open operator fun equals(other: Any?): Boolean public open fun hashCode(): Int public open fun toString(): String }
Il tipo Any
contiene i seguenti membri: equals ()
, hashcode ()
e anche toString ()
(simili a Java).
Le nostre classi non hanno bisogno di estendere esplicitamente questo tipo. Se non specificate esattamente quale classe si estende una nuova classe, la classe estende qualsiasi
implicitamente. Per questo motivo, di solito non è necessario includere: : Any
nel tuo codice - lo facciamo nel codice qui sopra per scopi dimostrativi.
Cerchiamo ora di creare classi in Kotlin con l'eredità in mente.
class Student { } class GraduateStudent : Student() { }
Nel codice di cui sopra, la classe GraduateStudent
estende la Superclass Student
. Ma questo codice non verrà compilato. Perché? Poiché le classi e i metodi sono definitive
per default in Kotlin. In altre parole, non possono essere estesi per impostazione predefinita, a differenza di Java dove le classi e i metodi sono aperti per impostazione predefinita.
Le migliori pratiche di ingegneria software raccomandano di iniziare a preparare le classi e i metodi per impostazione predefinita
, ad es. se non sono specificamente destinati ad essere ridefiniti o sovrascritti nelle sottoclassi. Il team di Kotlin (JetBrains) ha applicato questa filosofia di codifica e molte altre pratiche migliori per lo sviluppo nello sviluppo di questo linguaggio moderno.
Per consentire di creare sottoclassi da una superclasse, dobbiamo esplicitamente contrassegnare la superclasse con il modifier aperto
. Questo modificatore si applica anche a qualsiasi proprietà superclass o metodo che deve essere ignorato dalle sottoclassi.
open class Student { }
Abbiamo semplicemente messo il modificatore aperto
prima della parola chiave di classe
. Ora abbiamo istruito il compilatore per consentire alla nostra classe Studente
di essere aperto per l'estensione.
Come detto in precedenza, i membri di una classe di Kotlin sono anche definitivi per impostazione predefinita.
open class Student { open fun schoolFees(): BigDecimal { // do implementation } }
Nel codice precedente, abbiamo segnato la funzione schoolFees
come aperta
, in modo che le sottoclassi possano ignorarla.
open class GraduateStudent : Student() { override fun schoolFees(): BigDecimal { return super.schoolFees() + calculateSchoolFees() } private fun calculateSchoolFees(): BigDecimal { // calculate and return school fees } }
Qui, la funzione di scuola aperta
dalla Superclass Studente
viene superata dalla classe GraduateStudent
- aggiungendo il modificatore di override
prima della parola chiave divertente
. Si noti che se si ignora un membro di una superclasse o di un'interfaccia, l'utente prevalente sarà aperto
anche per impostazione predefinita, come nell'esempio seguente:
class ComputerScienceStudent : GraduateStudent() { override fun schoolFees(): BigDecimal { return super.schoolFees() + calculateSchoolFess() } private fun calculateSchoolFess(): BigDecimal { // calculate and return school fees } }
Anche se non abbiamo contrassegnato il metodo schoolFees
() nella classe GraduateStudent
con il modificatore aperto
, possiamo ancora ignorarlo - come abbiamo fatto nella classe ComputerScienceStudent
. Per evitare questo, dobbiamo contrassegnare l'ultimo membro come ultimo.
Ricorda che possiamo aggiungere nuove funzionalità a una classe - anche se è finale - con l'utilizzo di funzioni di estensione a Kotlin. Per un aggiornamento sulle funzioni di estensione, controlla le mie funzioni avanzate nel post di Kotlin. Inoltre, se hai bisogno di un aggiornamento su come regalare anche una classe finale nuove proprietà senza ereditarne, leggere la sezione sulle proprietà di estensione nel mio post di proprietà avanzate e classi.
- KotlinKotlin da zero: Funzioni avanzateChike Mgbemena
- KotlinKotlin da zero: Proprietà avanzate e classiChike Mgbemena
Se la nostra superclasse ha un costruttore principale come questo:
open class Student(val firstName: String, val lastName: String) { // ... }
Quindi ogni sottoclasse deve chiamare il costruttore principale della superclasse.
open class GraduateStudent(firstName: String, lastName: String) : Student(firstName, lastName) { // ... }
Possiamo semplicemente creare un oggetto della classe GraduateStudent
come al solito:
val graduateStudent = GraduateStudent("Jon", "Snow") println(graduateStudent.firstName) // Jon
Se la sottoclasse desidera chiamare il costruttore della superclasse dal suo costruttore secondario, utilizziamo la parola chiave super
(simile a come i costruttori di superclass sono invocati in Java).
open class GraduateStudent : Student { // ... private var thesis: String = "" constructor(firstName: String, lastName: String, thesis: String) : super(firstName, lastName) { this.thesis = thesis } }
Se hai bisogno di un aggiornamento sui costruttori di classe a Kotlin, visita la mia sezione Classi e oggetti.
4. Bonus: tipo Alias
Un'altra cosa impressionante che possiamo fare a Kotlin è dare un tipo un alias.
Vediamo un esempio.
data class Person(val firstName: String, val lastName: String, val age: Int)
Nella classe precedente, possiamo assegnare i tipi String
e Int
per gli alias delle proprietà di Persona
utilizzando il modificatore typealias
in Kotlin. Questo modificatore viene utilizzato per creare un alias di qualsiasi tipo in Kotlin, inclusi quelli creati.
typealias Name = String typealias Age = Int data class Person(val firstName: Name, val lastName: Name, val age: Age)
Come potete vedere, abbiamo creato un nome
e l'età
alias per entrambi i tipi String
e Int.
Ora abbiamo sostituito il tipo di proprietà firstName
e lastName
nel nostro Nome
alias e anche nel tipo Int
per l'alias di età
. Si noti che non abbiamo creato nuovi tipi, ma abbiamo creato un alias per i tipi.
Questi possono essere utili quando si desidera fornire un significato migliore o semantico ai tipi nella tua base di codice di Kotlin. Usatele con saggezza!
Conclusione
In questo tutorial, hai imparato di più sulla programmazione orientata agli oggetti a Kotlin. Abbiamo ricoperto quanto segue:
- classi astratte
- interfacce
- ereditarietà
- tipo alias
Se hai imparato Kotlin attraverso la nostra serie Kotlin From Scratch, assicuratevi di aver digitato il codice che vedi e l'esecuzione sul tuo IDE. Un'ottima soluzione per comprendere veramente un nuovo linguaggio di programmazione (o qualsiasi concetto di programmazione) che stai imparando è assicurarsi di non solo leggere la risorsa o la guida di apprendimento, ma anche digitare il codice effettivo e eseguirlo!
Nel tutorial successivo della serie Kotlin From Scratch, sarai introdotto nella gestione delle eccezioni a Kotlin. A presto!
Per saperne di più sulla lingua Kotlin, consiglio di visitare la documentazione di Kotlin. O controlla alcuni degli altri post di sviluppo di applicazioni Android qui su Envato Tuts!
- Android SDKJava vs Kotlin: dovresti usare Kotlin per lo sviluppo di Android?Jessica Thornsby
- Android SDKIntroduzione a componenti di Architettura di AndroidTin Megali
- Android SDKIniziare con RxJava 2 per AndroidJessica Thornsby
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post