Informazioni su questo codelab
1. Ti diamo il benvenuto.
In questo codelab, imparerai a convertire il codice da Java a Kotlin. Scoprirai anche quali sono le convenzioni linguistiche di Kotlin e come assicurarti che il codice che le scrivi sia seguito.
Questo codelab è adatto a qualsiasi sviluppatore che utilizza Java che sta valutando la migrazione del suo progetto a Kotlin. Inizieremo con un paio di lezioni di Java che tu convertirai in Kotlin utilizzando l'IDE. Poi daremo un'occhiata al codice convertito e vedremo come migliorarlo rendendolo più idiomatico ed evitando gli errori più comuni.
Obiettivi didattici
Imparerai a convertire Java in Kotlin. Durante questa operazione imparerai le seguenti caratteristiche e concetti relativi alla lingua Kotlin:
- Gestione della nullità
- Implementare i singleton
- Classi di dati
- Gestione delle stringhe
- Operatore Elvis
- Struttura
- Proprietà e proprietà di supporto
- Argomenti predefiniti e parametri denominati
- Utilizzare le collezioni
- Funzioni di estensione
- Funzioni e parametri di primo livello
- Parole chiave:
let
,apply
,with
erun
Premesse
Dovresti già conoscere Java.
Che cosa ti serve
2. Preparazione
Creare un nuovo progetto
Se utilizzi IntelliJ IDEA, crea un nuovo progetto Java con Kotlin/JVM.
Se usi Android Studio, crea un nuovo progetto senza attività.
Il codice
Creeremo un oggetto modello User
e una classe singleton Repository
che funzionano con gli oggetti User
ed espongono elenchi di utenti e nomi utente formattati.
Crea un nuovo file denominato User.java
in app/java/<nometuo pacchetto> e incolla il seguente codice:
public class User {
@Nullable
private String firstName;
@Nullable
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
A seconda del tipo di progetto, importa androidx.annotation.Nullable
se utilizzi un progetto Android oppure org.jetbrains.annotations.Nullable
se non lo utilizzi.
Crea un nuovo file denominato Repository.java
e incolla il seguente codice:
import java.util.ArrayList;
import java.util.List;
public class Repository {
private static Repository INSTANCE = null;
private List<User> users = null;
public static Repository getInstance() {
if (INSTANCE == null) {
synchronized (Repository.class) {
if (INSTANCE == null) {
INSTANCE = new Repository();
}
}
}
return INSTANCE;
}
// keeping the constructor private to enforce the usage of getInstance
private Repository() {
User user1 = new User("Jane", "");
User user2 = new User("John", null);
User user3 = new User("Anne", "Doe");
users = new ArrayList();
users.add(user1);
users.add(user2);
users.add(user3);
}
public List<User> getUsers() {
return users;
}
public List<String> getFormattedUserNames() {
List<String> userNames = new ArrayList<>(users.size());
for (User user : users) {
String name;
if (user.getLastName() != null) {
if (user.getFirstName() != null) {
name = user.getFirstName() + " " + user.getLastName();
} else {
name = user.getLastName();
}
} else if (user.getFirstName() != null) {
name = user.getFirstName();
} else {
name = "Unknown";
}
userNames.add(name);
}
return userNames;
}
}
3. Dichiarazione di nullità, val, var e classi di dati
Il nostro IDE può eseguire correttamente un processo di refactoring automatico del codice Java in codice Kotlin, ma a volte è necessario un piccolo aiuto. Lo faremo prima e poi passeremo in rassegna il codice di refactoring per comprendere come e perché in questo modo è stato eseguito il refactoring.
Vai al file User.java
e convertilo in Kotlin: Barra dei menu -> Codice -> Converti il file Java in un file Kotlin.
Se l'IDE richiede una correzione dopo la conversione, premi Sì.
Dovresti vedere il seguente codice Kotlin:
class User(var firstName: String?, var lastName: String?)
Tieni presente che il nome User.java
è stato rinominato in User.kt
. I file Kotlin hanno l'estensione .kt.
Nella nostra classe Java User
avevamo due proprietà: firstName
e lastName
. Ciascuno aveva un metodo getter e setter, che rendeva il proprio valore modificabile. La parola chiave di Kotlin per le variabili modificabili è var
, quindi l'utente che ha completato la conversione utilizza var
per ciascuna di queste proprietà. Se le nostre proprietà Java avessero getter, sarebbero immutabili e sarebbero state dichiarate come variabili val
. val
è simile alla parola chiave final
in Java.
Una delle principali differenze tra Kotlin e Java è che Kotlin specifica in modo esplicito se una variabile può accettare un valore nullo. Per farlo, aggiungi un elemento "?
" alla dichiarazione del tipo.
Poiché abbiamo contrassegnato firstName
e lastName
come null, l'autore della conversione automatica ha contrassegnato automaticamente le proprietà come null con String?
. Se annota i tuoi membri Java come non-null (utilizzando org.jetbrains.annotations.NotNull
o androidx.annotation.NonNull
), l'utente che ha completato la conversione riconosce anche questo e rende i campi non null in Kotlin.
Il refactoring di base è già stato eseguito. Tuttavia, possiamo farlo in modo più idiomatico. Vediamo come.
Classe dati
Il nostro corso User
contiene solo dati. Kotlin ha una parola chiave per le classi con questo ruolo: data
. Se contrassegni questa classe come classe data
, il compilatore creerà automaticamente getter e setter. Funzionerà anche nelle funzioni equals()
, hashCode()
e toString()
.
Aggiungiamo la parola chiave data
alla nostra classe User
:
data class User(var firstName: String, var lastName: String)
Kotlin, come Java, può avere un costruttore principale e uno o più costruttori secondari. Quello nell'esempio precedente è il costruttore principale della classe User. Se converti una classe Java che ha più costruttori, l'convertitore creerà automaticamente anche più costruttori in Kotlin. Sono definiti utilizzando la parola chiave constructor
.
Se vogliamo creare un'istanza di questo corso, possiamo farlo in questo modo:
val user1 = User("Jane", "Doe")
Parità
Kotlin ha due tipi di uguaglianza:
- L'uguaglianza strutturale usa l'operatore
==
e chiamaequals()
per determinare se due istanze sono uguali. - L'uguaglianza di riferimento utilizza l'operatore
===
e verifica se due riferimenti rimandano allo stesso oggetto.
Le proprietà definite nel costruttore principale della classe di dati verranno utilizzate per i controlli dell'uguaglianza strutturale.
val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false
4. Argomenti predefiniti, argomenti con nome
In Kotlin possiamo assegnare valori predefiniti agli argomenti nelle chiamate funzione. Il valore predefinito viene utilizzato quando l'argomento viene omesso. In Kotlin anche i costruttori sono funzioni, quindi possiamo utilizzare argomenti predefiniti per specificare che il valore predefinito di lastName
è null
. Per farlo, basta assegnare null
a lastName
.
data class User(var firstName: String?, var lastName: String? = null)
// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")
I parametri delle funzioni possono essere denominati quando chiamano le funzioni:
val john = User(firstName = "John", lastName = "Doe")
Come caso d'uso diverso, supponiamo che firstName
abbia null
come valore predefinito e lastName
no. In questo caso, poiché il parametro predefinito precede un parametro senza valore predefinito, devi chiamare la funzione con argomenti denominati:
data class User(var firstName: String? = null, var lastName: String?)
// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")
5. Inizializzazione di oggetti, oggetto companion e singleton
Prima di procedere, assicurati che la classe User
sia un corso data
. Convertiamo la classe Repository
in Kotlin. Il risultato della conversione automatica dovrebbe avere il seguente aspetto:
import java.util.*
class Repository private constructor() {
private var users: MutableList<User?>? = null
fun getUsers(): List<User?>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Vediamo cosa ha fatto l'utente che ha completato la conversione:
- È stato aggiunto un blocco
init
(Repository.kt#L50) - Ora il campo
static
fa parte di un bloccocompanion object
(Repository.kt#L33) - L'elenco di
users
è nullo poiché l'oggetto non è stato creato un'istanza al momento della dichiarazione (Repository.kt#L7) - Il metodo
getFormattedUserNames()
è ora una proprietà denominataformattedUserNames
(Repository.kt#L11) - L'iterazione sull'elenco di utenti (che inizialmente faceva parte di
getFormattedUserNames(
) ha una sintassi diversa da quella di Java (Repository.kt#L15)
Prima di continuare, salviamo un po' il codice. Abbiamo notato che l'autore della conversione ha reso il nostro elenco users
un elenco modificabile che contiene oggetti non validi. Anche se l'elenco può essere nullo, supponiamo che non possa contenere utenti non validi. Pertanto, eseguiamo le seguenti operazioni:
- Rimuovi l'elemento
?
inUser?
all'interno della dichiarazione del tipousers
getUsers
dovrebbe restituireList<User>?
L'autore della conversione automatica divide inutilmente anche in due righe le dichiarazioni delle variabili delle variabili utente e di quelle definite nel blocco init. Mettiamo ogni dichiarazione variabile su una sola riga. Ecco come dovrebbe apparire il codice:
class Repository private constructor() {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Blocco di inizializzazione
In Kotlin, il costruttore principale non può contenere alcun codice, quindi il codice di inizializzazione viene inserito nei blocchi init
. La funzionalità è la stessa.
class Repository private constructor() {
...
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Gran parte del codice init
gestisce le proprietà di inizializzazione. Puoi anche farlo nella dichiarazione della proprietà. Ad esempio, nella versione Kotlin della nostra classe Repository
, vediamo che la proprietà utente è stata inizializzata nella dichiarazione.
private var users: MutableList<User>? = null
static
Proprietà e metodi di Kotlin
In Java, utilizziamo la parola chiave static
per i campi o le funzioni per indicare che appartengono a una classe ma non a un'istanza della classe. Ecco perché abbiamo creato il campo statico INSTANCE
nella nostra classe Repository
. L'equivalente di Kotlin per questo problema è companion object
. Qui dichiari anche i campi statici e le funzioni statiche. L'utente che ha completato la conversione ha creato e spostato qui il campo INSTANCE
.
Gestire i singleton
Poiché ci serve una sola istanza della classe Repository
, abbiamo utilizzato il pattern singleton in Java. Con Kotlin puoi applicare questo pattern a livello di compilatore sostituendo la parola chiave class
con object
.
Rimuovi il costruttore privato e l'oggetto companion e sostituisci la definizione della classe con object Repository
.
object Repository {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String>
get() {
val userNames: MutableList<String> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Quando utilizzi la classe object
, chiamiamo funzioni e proprietà direttamente sull'oggetto, in questo modo:
val users = Repository.users
Struttura
Kotlin consente di strutturare un oggetto in una serie di variabili utilizzando una sintassi chiamata dichiarazione di destrutturazione. Creiamo più variabili e possiamo utilizzarle in modo indipendente.
Ad esempio, le classi di dati supportano la strutturazione in modo da poter destrutturare l'oggetto User
nel loop di for
in (firstName, lastName)
. In questo modo possiamo lavorare direttamente con i valori firstName
e lastName
. Aggiorna il loop for
in questo modo:
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
6. Gestione della nullità
Durante la conversione della classe Repository
in Kotlin, il convertitore automatico ha reso nulla l'elenco degli utenti, perché non è stato inizializzato in un oggetto quando è stato dichiarato. Per tutti gli utilizzi dell'oggetto users
, viene utilizzato l'operatore dell'asserzione non null !!
. Converte qualsiasi variabile in un tipo non null e genera un'eccezione se il valore è null. Se utilizzi !!
, rischi di generare eccezioni in fase di runtime.
Tuttavia, preferisci gestire la nullità utilizzando uno dei seguenti metodi:
- Verifica null (
if (users != null) {...}
) - Utilizzo dell'operatore Elvis
?:
(trattato in seguito nel codelab) - Utilizzo di alcune funzioni standard di Kotlin (trattate in seguito nel codelab)
Nel nostro caso, sappiamo che l'elenco degli utenti non deve essere nullo, dato che viene inizializzato subito dopo la creazione dell'oggetto, quindi possiamo creare un'istanza diretta dell'oggetto quando lo dichiariamo.
Durante la creazione di istanze dei tipi di raccolta, Kotlin offre diverse funzioni helper per rendere il codice più leggibile e flessibile. Stiamo utilizzando MutableList
per users
:
private var users: MutableList<User>? = null
Per semplicità, possiamo utilizzare la funzione mutableListOf()
, fornire il tipo di elemento elenco, rimuovere la chiamata costruttore ArrayList
dal blocco init
e rimuovere la dichiarazione del tipo esplicito della proprietà users
.
private val users = mutableListOf<User>()
Abbiamo inoltre modificato var in val perché gli utenti conterranno un riferimento immutabile all'elenco di utenti. Tieni presente che il riferimento è immutabile, ma l'elenco stesso è modificabile (puoi aggiungere o rimuovere elementi).
Con queste modifiche, la nostra proprietà users
ora non è null e possiamo rimuovere tutte le occorrenze dell'operatore !!
non necessarie.
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
...
}
Inoltre, poiché la variabile utente è già inizializzata, dobbiamo rimuovere l'inizializzazione dal blocco init
:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
Poiché sia lastName
sia firstName
possono essere null
, dobbiamo gestire la nullità quando creiamo l'elenco di nomi utente formattati. Poiché vogliamo mostrare "Unknown"
se manca uno dei due nomi, possiamo rendere il nome non nullo rimuovendo ?
dalla dichiarazione del tipo.
val name: String
Se lastName
è null, name
è firstName
o "Unknown"
:
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
Questo può essere scritto in modo più descrittivo utilizzando l'operatore Elvis ?:
. L'operatore elvis restituirà l'espressione sul lato sinistro se non è null o l'espressione sul lato destro, se il lato sinistro è null.
Quindi, nel seguente codice, user.firstName
viene restituito se non è nullo. Se user.firstName
è null, l'espressione restituisce il valore sulla destra , "Unknown"
:
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
7. Modelli di stringa e espressione
Kotlin semplifica la collaborazione con gli String
grazie ai modelli di stringhe. I modelli di stringa consentono di fare riferimento alle variabili all'interno delle dichiarazioni stringa.
Il convertitore automatico ha aggiornato la concatenazione del nome e del cognome per fare riferimento al nome della variabile direttamente nella stringa utilizzando il simbolo $
e inserendo l'espressione tra { }
.
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"
Nel codice, sostituisci la concatenazione di stringhe con:
name = "$firstName $lastName"
In Kotlin if
, when
, for
e while
sono espressioni, che restituiscono un valore. Il tuo IDE mostra persino un avviso indicante che il compito deve essere rimosso dal if
:
Segui il suggerimento di IDE e rimuovi il compito per entrambe le istruzioni if
. L'ultima riga dell'istruzione if verrà assegnata. In questo modo è più chiaro che il solo scopo di questo blocco è inizializzare il valore del nome:
name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
Successivamente, riceverai un avviso che ti informa che la dichiarazione name
può essere aggiunta al compito. Viene applicata anche questa impostazione. Poiché è possibile dedurre il tipo della variabile name, possiamo rimuovere la dichiarazione del tipo esplicito. Ora il nostro formattedUserNames
ha il seguente aspetto:
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
8. Operazioni sulle collezioni
Diamo un'occhiata più da vicino al getter formattedUserNames
e vediamo come possiamo renderlo più idiomatico. Al momento il codice funziona nel seguente modo:
- Crea un nuovo elenco di stringhe
- Itera l'elenco di utenti
- Crea il nome formattato per ogni utente, in base al nome e al cognome dell'utente.
- Restituisce l'elenco appena creato
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
Kotlin fornisce un vasto elenco di trasformazioni delle raccolte che rendono lo sviluppo più rapido e sicuro espandendo le funzionalità dell'API Java collections. Una è la funzione map
. Questa funzione restituisce un nuovo elenco contenente i risultati dell'applicazione della funzione di trasformazione specificata a ciascun elemento dell'elenco originale. Quindi, anziché creare un nuovo elenco e ripeterlo manualmente, possiamo utilizzare la funzione map
e spostare la logica che era presente nel loop di for
all'interno del corpo di map
. Per impostazione predefinita, il nome dell'elemento dell'elenco corrente utilizzato in map
è it
, ma per una migliore leggibilità puoi sostituire it
con il nome della tua variabile. Nel nostro caso, lo chiamiamo user
:
val formattedUserNames: List<String>
get() {
return users.map { user ->
val name = if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
name
}
}
Per semplificare ulteriormente questo passaggio, possiamo rimuovere completamente la variabile name
:
val formattedUserNames: List<String>
get() {
return users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
9. Proprietà e proprietà di supporto
Abbiamo notato che il convertitore automatico ha sostituito la funzione getFormattedUserNames()
con una proprietà denominata formattedUserNames
con un getter personalizzato. Di seguito viene generato un metodo getFormattedUserNames()
che restituisce un List
.
In Java, esporremmo le nostre proprietà di classe tramite le funzioni getter e setter. Kotlin ci permette di avere una differenziazione migliore delle proprietà di una classe, espressa con campi e funzionalità, azioni che una classe può fare, espresse con le funzioni. Nel nostro caso, il corso Repository
è molto semplice e non esegue alcuna azione, quindi contiene solo campi.
La logica che è stata attivata nella funzione getFormattedUserNames()
di Java viene attivata quando si chiama il getter della proprietà Kotlin formattedUserNames
.
Anche se non abbiamo esplicitamente un campo corrispondente alla proprietà formattedUserNames
, Kotlin ci fornisce un campo di supporto automatico chiamato field
a cui possiamo accedere se necessario da getter e setter personalizzati.
Tuttavia, a volte vogliamo alcune funzionalità aggiuntive che il campo di supporto automatico non fornisce. Diamo un'occhiata a un esempio qui sotto.
All'interno della nostra classe Repository
, abbiamo un elenco modificabile di utenti che vengono esposti nella funzione getUsers()
generata dal nostro codice Java:
fun getUsers(): List<User>? {
return users
}
Il problema è che se restituisci users
qualsiasi consumatore della classe Repository può modificare il nostro elenco di utenti, non è una buona idea. Risolviamo il problema utilizzando una proprietà di supporto.
Innanzitutto, rinomina users
con _users
. Ora aggiungi una proprietà immutabile pubblica che restituisce un elenco di utenti. Chiamiamola users
:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
Con questa modifica, la proprietà privata _users
diventa la proprietà di supporto della proprietà pubblica users
. Al di fuori del corso Repository
, l'elenco _users
non è modificabile, poiché i consumatori del corso possono accedervi solo tramite users
.
Codice completo:
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
10. Funzioni e proprietà di primo livello e di estensione
In questo momento la classe Repository
sa come calcolare il nome utente formattato per un oggetto User
. Tuttavia, se vogliamo riutilizzare la stessa logica di formattazione in altri corsi, dobbiamo copiarla e incollarla o spostarla nella classe User
.
Kotlin consente di dichiarare funzioni e proprietà al di fuori di qualsiasi classe, oggetto o interfaccia. Ad esempio, la funzione mutableListOf()
che abbiamo utilizzato per creare una nuova istanza di un elemento List
è definita direttamente in Collections.kt
dalla libreria standard.
In Java, ogni volta che hai bisogno di una funzionalità di utilità, è molto probabile che crei una classe Util
e dichiari come funzionalità statica. In Kotlin puoi dichiarare le funzioni di primo livello, senza avere una classe. Tuttavia, Kotlin consente anche di creare funzioni di estensione. Si tratta di funzioni che estendono un determinato tipo, ma che sono dichiarate al di fuori del tipo. Di conseguenza, hanno un'affinità con quel tipo.
La visibilità delle funzioni e delle proprietà delle estensioni può essere limitata utilizzando i modificatori di visibilità. Questi limiti limitano l'utilizzo alle classi che ne hanno bisogno e non influiscono sullo spazio dei nomi.
Per la classe User
, possiamo aggiungere una funzione di estensione che calcola il nome formattato oppure possiamo mantenere il nome formattato in una proprietà dell'estensione. Può essere aggiunto al di fuori della classe Repository
, nello stesso file:
// extension function
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// extension property
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName
Possiamo quindi utilizzare le funzioni e le proprietà dell'estensione come se fossero parte della classe User
.
Poiché il nome formattato è una proprietà dell'utente e non una funzionalità della classe Repository
, utilizziamo la proprietà dell'estensione. Il nostro file Repository
ora ha il seguente aspetto:
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user -> user.formattedName }
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
La libreria standard di Kotlin utilizza le funzioni delle estensioni per estendere la funzionalità di diverse API Java; molte delle funzionalità su Iterable
e Collection
sono implementate come funzioni di estensione. Ad esempio, la funzione map
che abbiamo utilizzato in un passaggio precedente è una funzione di estensione su Iterable
.
11. Funzioni di ambito: autorizza, applica, esegui, anche
Nel codice del corso Repository
, stiamo aggiungendo diversi oggetti utente all'elenco _users
. Queste chiamate possono essere rese più idiomatiche con l'aiuto delle funzioni di ambito.
Per eseguire il codice solo nel contesto di un oggetto specifico, senza dover accedere all'oggetto in base al nome, Kotlin ha creato 5 funzioni di ambito: let
, apply
, with
, run
e also
. Breve e potente, tutte queste funzioni hanno un ricevitore (this
), possono avere un argomento (it
) e possono restituire un valore. Puoi decidere quale utilizzare, in base agli obiettivi che vuoi raggiungere.
Ecco una scheda di riferimento utile per aiutarti a ricordarla:
Poiché stiamo configurando il nostro oggetto _users
nel nostro Repository
, possiamo rendere il codice più idiomatico utilizzando la funzione apply
:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.apply {
// this == _users
add(user1)
add(user2)
add(user3)
}
}
12. Conclusione
In questo codelab, abbiamo visto le nozioni di base necessarie per iniziare a refactoring del codice da Java a Kotlin. Questo refactoring è indipendente dalla piattaforma di sviluppo e consente di garantire che il codice scritto sia idiomatico.
Idiomatico Kotlin rende la scrittura di codice breve e piacevole. Con tutte le funzionalità offerte da Kotlin, ci sono molti modi per rendere il tuo codice più sicuro, conciso e più leggibile. Ad esempio, possiamo anche ottimizzare la nostra classe Repository
creando un'istanza dell'elenco _users
con gli utenti direttamente nella dichiarazione, eliminando il blocco init
:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
Abbiamo trattato un'ampia gamma di argomenti, dalla gestione di nullità, singleton, stringhe e raccolte ad argomenti quali le funzioni di estensione, di primo livello, di proprietà e di ambito. Siamo passati da due classi Java a due Kotlin simili alla seguente:
User.kt
class User(var firstName: String?, var lastName: String?)
Repository.kt
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() = _users.map { user -> user.formattedName }
}
Ecco un TL;DR delle funzionalità Java e la relativa mappatura su Kotlin:
Java | Kotlin |
|
|
|
|
|
|
Classe contenente solo i dati |
|
Inizializzazione nel costruttore | Inizializzazione nel blocco |
| campi e funzioni dichiarati in un elemento |
Classe Singleton |
|
Per saperne di più su Kotlin e su come utilizzarlo sulla tua piattaforma, consulta queste risorse:
- Kan di Kotlin
- Tutorial su Kotlin
- Sviluppo di app Android con Kotlin: corso senza costi
- Bootcamp Kotlin per programmatori
- Kotlin per sviluppatori Java: corso senza costi in modalità di controllo